Add image show random app

This commit is contained in:
2025-05-29 21:25:17 +10:00
parent 7b52efda21
commit 34ac2de5ca
7 changed files with 731 additions and 2 deletions
+73
View File
@@ -0,0 +1,73 @@
from django.views.generic import TemplateView
from django.shortcuts import render
class MiniAppsListView(TemplateView):
template_name = 'links/mini_apps/list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Define mini apps with their info
mini_apps = [
{
'name': 'Image Gallery',
'description': 'A beautiful fullscreen image slideshow application with smooth transitions and auto-play functionality.',
'url': 'mini-apps-image-gallery',
'thumbnail': 'https://picsum.photos/seed/picsum/400/300?fit=crop',
'icon': 'fas fa-images',
'color': '#3498db'
},
{
'name': 'Weather Dashboard',
'description': 'Real-time weather information with beautiful visualizations and forecasts.',
'url': '#',
'thumbnail': 'https://images.unsplash.com/photo-1504608524841-42fe6f032b4b?w=400&h=300&fit=crop',
'icon': 'fas fa-cloud-sun',
'color': '#e74c3c'
},
{
'name': 'Music Player',
'description': 'Stream and organize your favorite music with an elegant interface.',
'url': '#',
'thumbnail': 'https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=400&h=300&fit=crop',
'icon': 'fas fa-music',
'color': '#9b59b6'
},
{
'name': 'Task Manager',
'description': 'Organize your tasks and boost productivity with smart scheduling.',
'url': '#',
'thumbnail': 'https://images.unsplash.com/photo-1484480974693-6ca0a78fb36b?w=400&h=300&fit=crop',
'icon': 'fas fa-tasks',
'color': '#2ecc71'
},
{
'name': 'Code Editor',
'description': 'A lightweight code editor with syntax highlighting and themes.',
'url': '#',
'thumbnail': 'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=400&h=300&fit=crop',
'icon': 'fas fa-code',
'color': '#f39c12'
},
{
'name': 'Calculator',
'description': 'Scientific calculator with advanced mathematical functions.',
'url': '#',
'thumbnail': 'https://images.unsplash.com/photo-1611224923853-80b023f02d71?w=400&h=300&fit=crop',
'icon': 'fas fa-calculator',
'color': '#1abc9c'
}
]
context['mini_apps'] = mini_apps
return context
class ImageGalleryView(TemplateView):
template_name = 'links/mini_apps/image_gallery.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add any context data needed for the image gallery
return context
@@ -0,0 +1,560 @@
{% extends 'base_blank.html' %}
{% load i18n %}
{% block title %}{% trans "Image Gallery" %} - {% trans "GoLinks" %}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
overflow: hidden;
height: 100vh;
background-color: #0a0a0a;
color: #fff;
}
.app-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.slideshow-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
opacity: 0;
transition: none;
will-change: transform, opacity;
}
.slide.active {
opacity: 1;
z-index: 2;
}
/* Animation effects */
.fade {
transition: opacity 1.5s ease-in-out;
}
.slide-left {
transform: translateX(100%);
}
.slide-left.active {
transform: translateX(0);
transition: transform 1.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.slide-right {
transform: translateX(-100%);
}
.slide-right.active {
transform: translateX(0);
transition: transform 1.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.zoom {
transform: scale(1.2);
}
.zoom.active {
transform: scale(1);
transition: transform 1.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.control-panel {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
border-radius: 50px;
padding: 15px 25px;
display: flex;
gap: 20px;
z-index: 10;
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.control-group {
display: flex;
flex-direction: column;
align-items: center;
}
.control-label {
font-size: 13px;
margin-bottom: 5px;
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
}
select, input {
background: rgba(50, 50, 50, 0.6);
border: 1px solid rgba(255, 255, 255, 0.1);
color: white;
padding: 8px 15px;
border-radius: 25px;
min-width: 120px;
font-size: 14px;
outline: none;
cursor: pointer;
}
select:focus, input:focus {
border-color: rgba(255, 255, 255, 0.3);
}
option {
background: #222;
}
.btn {
background: rgba(50, 50, 50, 0.6);
border: 1px solid rgba(255, 255, 255, 0.1);
color: white;
padding: 8px 15px;
border-radius: 25px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.btn:hover {
background: rgba(70, 70, 70, 0.8);
border-color: rgba(255, 255, 255, 0.3);
}
.title {
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
text-align: center;
z-index: 5;
background: rgba(0, 0, 0, 0.5);
padding: 15px 40px;
border-radius: 30px;
font-size: 24px;
font-weight: 300;
letter-spacing: 1px;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.title span {
color: #ff6b6b;
font-weight: 600;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 20;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-top: 4px solid #4ecdc4;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
letter-spacing: 1px;
}
.app-logo {
position: absolute;
top: 30px;
left: 30px;
z-index: 5;
font-size: 24px;
font-weight: 700;
color: white;
display: flex;
align-items: center;
gap: 10px;
}
.app-logo i {
color: #ff6b6b;
}
.timer-display {
position: absolute;
top: 30px;
right: 30px;
background: rgba(0, 0, 0, 0.5);
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
z-index: 5;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.progress-bar {
position: absolute;
top: 0;
left: 0;
height: 4px;
background: linear-gradient(90deg, #ff6b6b, #4ecdc4);
z-index: 15;
width: 0%;
transition: width 0.1s linear;
}
.preview-title {
margin-bottom: 10px;
font-size: 16px;
color: #4ecdc4;
}
.preview-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.preview-item {
height: 60px;
border-radius: 8px;
background-size: cover;
background-position: center;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.preview-item:hover {
transform: translateY(-3px);
}
.preview-item.active {
border-color: #ff6b6b;
box-shadow: 0 0 15px rgba(255, 107, 107, 0.5);
}
.back-button {
position: absolute;
top: 30px;
right: 100px;
background: rgba(0, 0, 0, 0.5);
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
z-index: 5;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
color: white;
text-decoration: none;
transition: all 0.3s ease;
}
.back-button:hover {
background: rgba(50, 50, 50, 0.8);
color: #4ecdc4;
text-decoration: none;
}
@media (max-width: 768px) {
.control-panel {
flex-direction: column;
border-radius: 20px;
bottom: 20px;
padding: 15px;
}
.title {
font-size: 18px;
top: 20px;
padding: 10px 20px;
width: 90%;
}
.photo-info {
width: 90%;
text-align: center;
bottom: 150px;
}
.back-button {
right: 30px;
top: 80px;
padding: 6px 12px;
font-size: 12px;
}
}
</style>
{% endblock %}
{% block content %}
<div class="app-container">
<div class="progress-bar"></div>
<div class="app-logo">
<i class="fas fa-images"></i>
<span>RandomImage</span>
</div>
<a href="{% url 'mini-apps-list' %}" class="back-button">
<i class="fas fa-arrow-left"></i> {% trans "Back to Mini Apps" %}
</a>
<div class="slideshow-container" id="slideshow"></div>
<div class="control-panel">
<div class="control-group">
<div class="control-label">{% trans "Animation Effect" %}</div>
<select id="animationType">
<option value="fade">{% trans "Fade In/Out" %}</option>
<option value="slide-left">{% trans "Slide Left" %}</option>
<option value="slide-right">{% trans "Slide Right" %}</option>
<option value="zoom">{% trans "Zoom Effect" %}</option>
</select>
</div>
<div class="control-group">
<div class="control-label">{% trans "Interval (seconds)" %}</div>
<input type="number" id="interval" min="5" max="60" value="15">
</div>
<div class="control-group">
<div class="control-label">{% trans "Actions" %}</div>
<button class="btn" id="nextBtn">
<i class="fas fa-forward"></i> {% trans "Next" %}
</button>
</div>
</div>
<div class="timer-display">
<i class="fas fa-clock"></i>
<span id="timer">00:15</span>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<div class="loading-text">{% trans "Loading..." %}</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Application configuration
const config = {
animationType: 'fade',
interval: 15000, // 15 seconds
currentSlide: null,
nextSlide: null,
timer: null,
countdown: null
};
// DOM elements
const dom = {
slideshow: document.getElementById('slideshow'),
animationType: document.getElementById('animationType'),
intervalInput: document.getElementById('interval'),
nextBtn: document.getElementById('nextBtn'),
loading: document.getElementById('loading'),
timerDisplay: document.getElementById('timer'),
progressBar: document.querySelector('.progress-bar'),
previewItems: document.querySelectorAll('.preview-item')
};
// Initialize application
function initApp() {
// Set event listeners
dom.animationType.addEventListener('change', updateAnimationType);
dom.intervalInput.addEventListener('change', updateInterval);
dom.nextBtn.addEventListener('click', loadNextImage);
// Add animation preview click events
dom.previewItems.forEach(item => {
item.addEventListener('click', function() {
// Remove all active classes
dom.previewItems.forEach(i => i.classList.remove('active'));
// Add active class to current item
this.classList.add('active');
// Update animation type
config.animationType = this.dataset.animation;
dom.animationType.value = config.animationType;
});
});
// Load first image
loadNextImage();
}
// Update animation type
function updateAnimationType() {
config.animationType = dom.animationType.value;
// Update preview selection
dom.previewItems.forEach(item => {
item.classList.remove('active');
if (item.dataset.animation === config.animationType) {
item.classList.add('active');
}
});
}
// Update interval
function updateInterval() {
const seconds = parseInt(dom.intervalInput.value);
if (seconds >= 5 && seconds <= 60) {
config.interval = seconds * 1000;
resetTimer();
} else {
alert('{% trans "Interval must be between 5 and 60 seconds" %}');
dom.intervalInput.value = config.interval / 1000;
}
}
// Reset timer
function resetTimer() {
clearInterval(config.timer);
clearInterval(config.countdown);
startTimer();
startCountdown();
}
// Start slideshow timer
function startTimer() {
config.timer = setInterval(loadNextImage, config.interval);
}
// Start countdown display
function startCountdown() {
clearInterval(config.countdown);
let remaining = config.interval / 1000;
updateTimerDisplay(remaining);
config.countdown = setInterval(() => {
remaining--;
if (remaining <= 0) {
remaining = config.interval / 1000;
}
updateTimerDisplay(remaining);
// Update progress bar
const progress = 100 - (remaining / (config.interval / 1000) * 100);
dom.progressBar.style.width = `${progress}%`;
}, 1000);
}
// Update timer display
function updateTimerDisplay(seconds) {
dom.timerDisplay.textContent = `00:${seconds.toString().padStart(2, '0')}`;
}
// Get random image ID (between 1000-1084)
function getRandomImageId() {
return Math.floor(Math.random() * 85) + 1000;
}
// Load next image
async function loadNextImage() {
dom.loading.style.display = 'flex';
try {
// Get random image ID
const imageId = getRandomImageId();
const imgUrl = `https://picsum.photos/id/${imageId}/${window.innerWidth}/${window.innerHeight}`;
// Create new slide
const slide = document.createElement('div');
slide.className = `slide ${config.animationType}`;
slide.style.backgroundImage = `url(${imgUrl})`;
// Add to slideshow container
dom.slideshow.appendChild(slide);
// Wait for image to load
await new Promise((resolve) => {
const img = new Image();
img.src = imgUrl;
img.onload = resolve;
});
// Show new slide
setTimeout(() => {
// Remove current active slide
if (config.currentSlide) {
config.currentSlide.classList.remove('active');
// Remove element after animation ends
setTimeout(() => {
if (config.currentSlide && !config.currentSlide.classList.contains('active')) {
config.currentSlide.remove();
}
}, 2000);
}
// Set new slide as active
slide.classList.add('active');
config.currentSlide = slide;
dom.loading.style.display = 'none';
}, 100);
// Reset timer
resetTimer();
} catch (error) {
console.error('Failed to load image:', error);
dom.loading.innerHTML = '<div class="loading-text">{% trans "Image loading failed, please retry" %}</div>';
setTimeout(() => {
dom.loading.style.display = 'none';
loadNextImage();
}, 2000);
}
}
// Initialize app when page loads
window.addEventListener('DOMContentLoaded', initApp);
</script>
{% endblock %}
+81
View File
@@ -0,0 +1,81 @@
{% extends 'base.html' %}
{% load i18n %}
{% block content %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Header Section -->
<div class="text-center mb-12">
<h1 class="text-4xl font-bold text-gray-900 mb-4">
<i class="fas fa-rocket mr-3 text-blue-600"></i>
{% trans "Mini Apps" %}
</h1>
<p class="text-xl text-gray-600 max-w-3xl mx-auto">
{% trans "Discover a collection of powerful mini applications designed to enhance your productivity and creativity." %}
</p>
</div>
<!-- Apps Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-16">
{% for app in mini_apps %}
<div class="group relative overflow-hidden rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2 mb-8">
<!-- Thumbnail -->
<div class="aspect-video overflow-hidden">
<img src="{{ app.thumbnail }}"
alt="{{ app.name }}"
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300">
</div>
<!-- Overlay Gradient -->
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent"></div>
<!-- Content -->
<div class="absolute inset-0 flex flex-col justify-end p-6 text-white">
<!-- Icon and Title -->
<div class="flex items-center mb-3">
<div class="w-12 h-12 rounded-full flex items-center justify-center mr-3"
style="background-color: {{ app.color }};">
<i class="{{ app.icon }} text-white text-xl"></i>
</div>
<h3 class="text-2xl font-bold">{{ app.name }}</h3>
</div>
<!-- Description -->
<p class="text-gray-200 text-sm mb-4 leading-relaxed">
{{ app.description }}
</p>
<!-- Action Button -->
{% if app.url != "#" %}
<a href="{% url app.url %}"
class="inline-flex items-center justify-center px-6 py-3 rounded-lg font-medium transition-all duration-200 transform hover:scale-105"
style="background-color: {{ app.color }};">
<i class="fas fa-play mr-2"></i>
{% trans "Launch App" %}
</a>
{% else %}
<div class="inline-flex items-center justify-center px-6 py-3 rounded-lg font-medium bg-gray-600 cursor-not-allowed">
<i class="fas fa-lock mr-2"></i>
{% trans "Coming Soon" %}
</div>
{% endif %}
</div>
<!-- Hover Effect -->
<div class="absolute inset-0 bg-gradient-to-t from-transparent via-transparent to-white/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div>
</div>
{% endfor %}
</div>
</div>
<style>
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.group:hover .float {
animation: float 2s ease-in-out infinite;
}
</style>
{% endblock %}
+5
View File
@@ -4,6 +4,7 @@ from . import page_views
from . import search_views
from . import post_views
from . import collection_views
from . import mini_apps_views
from django.views.generic import TemplateView
urlpatterns = [
@@ -46,6 +47,10 @@ urlpatterns = [
collection_views.CollectionSlideshowView.as_view(),
name='collection-slideshow'),
# Mini Apps
path('ui/mini-apps/', mini_apps_views.MiniAppsListView.as_view(), name='mini-apps-list'),
path('ui/mini-apps/image-gallery/', mini_apps_views.ImageGalleryView.as_view(), name='mini-apps-image-gallery'),
# API Documentation
path('ui/api-docs/',
TemplateView.as_view(template_name='links/api_docs.html'),
+1 -1
View File
@@ -1,5 +1,5 @@
#!/bin/bash
set -eu
source 3.12/bin/activate
source .venv/bin/activate
echo "Starting Django development server..."
python manage.py runserver
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
set -eu
source 3.12/bin/activate
source .venv/bin/activate
echo "Starting Tailwind CSS compiler..."
python manage.py tailwind start
+10
View File
@@ -139,6 +139,16 @@
</div>
</a>
<a href="{% url 'mini-apps-list' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<div class="flex items-center">
<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="M19 11H5m14-4H5a2 2 0 00-2 2v6a2 2 0 002 2h14M9 7h1m4 0h1M9 17h1m4 0h1"/>
</svg>
{% trans "Mini Apps" %}
</div>
</a>
<a href="{% url 'tools' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<div class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">