mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 03:51:09 -05:00
- Implemented park card component with image, status badge, favorite button, and quick stats overlay. - Developed ride card component featuring thrill level badge, status badge, favorite button, and detailed stats. - Created advanced search page with filters for parks and rides, including location, type, status, and thrill level. - Added dynamic quick search functionality with results display. - Enhanced user experience with JavaScript for filter toggling, range slider updates, and view switching. - Included custom CSS for improved styling of checkboxes and search results layout.
92 lines
3.1 KiB
JavaScript
92 lines
3.1 KiB
JavaScript
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('photoDisplay', ({ photos, contentType, objectId, csrfToken, uploadUrl }) => ({
|
|
photos,
|
|
fullscreenPhoto: null,
|
|
uploading: false,
|
|
uploadProgress: 0,
|
|
error: null,
|
|
showSuccess: false,
|
|
|
|
showFullscreen(photo) {
|
|
this.fullscreenPhoto = photo;
|
|
},
|
|
|
|
async handleFileSelect(event) {
|
|
const files = Array.from(event.target.files);
|
|
if (!files.length) {
|
|
return;
|
|
}
|
|
|
|
this.uploading = true;
|
|
this.uploadProgress = 0;
|
|
this.error = null;
|
|
this.showSuccess = false;
|
|
|
|
const totalFiles = files.length;
|
|
let completedFiles = 0;
|
|
|
|
for (const file of files) {
|
|
const formData = new FormData();
|
|
formData.append('image', file);
|
|
formData.append('app_label', contentType.split('.')[0]);
|
|
formData.append('model', contentType.split('.')[1]);
|
|
formData.append('object_id', objectId);
|
|
|
|
try {
|
|
const response = await fetch(uploadUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken,
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
throw new Error(data.error || 'Upload failed');
|
|
}
|
|
|
|
const photo = await response.json();
|
|
this.photos.push(photo);
|
|
completedFiles++;
|
|
this.uploadProgress = (completedFiles / totalFiles) * 100;
|
|
} catch (err) {
|
|
this.error = err.message || 'Failed to upload photo. Please try again.';
|
|
console.error('Upload error:', err);
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.uploading = false;
|
|
event.target.value = ''; // Reset file input
|
|
|
|
if (!this.error) {
|
|
this.showSuccess = true;
|
|
setTimeout(() => {
|
|
this.showSuccess = false;
|
|
}, 3000);
|
|
}
|
|
},
|
|
|
|
async sharePhoto(photo) {
|
|
if (navigator.share) {
|
|
try {
|
|
await navigator.share({
|
|
title: photo.caption || 'Shared photo',
|
|
url: photo.url
|
|
});
|
|
} catch (err) {
|
|
if (err.name !== 'AbortError') {
|
|
console.error('Error sharing:', err);
|
|
}
|
|
}
|
|
} else {
|
|
// Fallback: copy URL to clipboard
|
|
navigator.clipboard.writeText(photo.url)
|
|
.then(() => alert('Photo URL copied to clipboard!'))
|
|
.catch(err => console.error('Error copying to clipboard:', err));
|
|
}
|
|
}
|
|
}));
|
|
});
|