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)); } } })); });