1/** 2 * Photo and Features Handler for SparkleTour 3 * Handles photo preview and feature checkbox functionality 4 */ 5 6(function($) { 7 'use strict'; 8 9 var features = { 10 popular: [], 11 categories: { 12 lighting: [], 13 elements: [], 14 special: [], 15 accessibility: [] 16 } 17 }; 18 19 /** 20 * Initialize photo and features functionality 21 */ 22 function init() { 23 loadFeatures(); 24 setupPhotoPreview(); 25 setupFeatureToggle(); 26 setupFeatureChipInteraction(); 27 } 28 29 /** 30 * Load features from API 31 */ 32 function loadFeatures() { 33 $.ajax({ 34 url: '/api/features.php', 35 type: 'GET', 36 dataType: 'json', 37 success: function(response) { 38 if (response.success) { 39 features = response.data; 40 renderFeatures(); 41 } 42 }, 43 error: function(xhr, status, error) { 44 console.error('[PhotoFeatures] Error loading features:', error); 45 } 46 }); 47 } 48 49 /** 50 * Render feature checkboxes 51 */ 52 function renderFeatures() { 53 // Render popular features 54 var popularHtml = ''; 55 features.popular.forEach(function(feature) { 56 popularHtml += createFeatureCheckbox(feature); 57 }); 58 $('#popular-features').html(popularHtml); 59 60 // Render category features 61 $.each(features.categories, function(category, categoryFeatures) { 62 var categoryHtml = ''; 63 categoryFeatures.forEach(function(feature) { 64 categoryHtml += createFeatureCheckbox(feature); 65 }); 66 $('#' + category + '-features .feature-checkboxes').html(categoryHtml); 67 }); 68 } 69 70 /** 71 * Create feature checkbox HTML 72 * Uses chip-style buttons for better mobile experience 73 */ 74 function createFeatureCheckbox(feature) { 75 var icon = feature.icon ? '<i class="fa ' + feature.icon + ' me-1"></i>' : ''; 76 return '<div class="feature-chip-wrapper">' + 77 '<input type="checkbox" name="features[]" value="' + feature.slug + '" class="feature-checkbox d-none" id="feature-' + feature.slug + '">' + 78 '<label for="feature-' + feature.slug + '" class="feature-chip-label">' + 79 icon + feature.name + 80 '</label>' + 81 '</div>'; 82 } 83 84 /** 85 * Setup photo preview functionality 86 */ 87 function setupPhotoPreview() { 88 // Use event delegation to handle dynamically added buttons 89 $(document).on('click', '#photo-upload-btn', function() { 90 var $fileInput = $('#display_photo'); 91 // Remove capture attribute to allow gallery selection 92 $fileInput.removeAttr('capture'); 93 $fileInput.click(); 94 }); 95 96 // Take Photo button - opens camera directly (mobile only) 97 $(document).on('click', '#photo-camera-btn', function() { 98 var $fileInput = $('#display_photo'); 99 // Add capture attribute to force camera 100 $fileInput.attr('capture', 'environment'); 101 $fileInput.click(); 102 }); 103 104 $('#display_photo').on('change', function(e) { 105 var file = e.target.files[0]; 106 107 if (!file) { 108 $('#photo-preview').html(''); 109 $('#photo-filename').html(''); 110 $('#photo-upload-btn').html('<i class="bi bi-image me-2"></i>Choose Photo'); 111 $('#photo-camera-btn').html('<i class="bi bi-camera-fill me-2"></i>Take Photo'); 112 return; 113 } 114 115 // Validate file type 116 var validTypes = ['image/jpeg', 'image/png', 'image/webp']; 117 if (validTypes.indexOf(file.type) === -1) { 118 $('#photo-preview').html('<div class="alert alert-danger">Invalid file type. Please select a JPEG, PNG, or WebP image.</div>'); 119 $('#photo-filename').html(''); 120 $('#photo-upload-btn').html('<i class="bi bi-image me-2"></i>Choose Photo'); 121 $('#photo-camera-btn').html('<i class="bi bi-camera-fill me-2"></i>Take Photo'); 122 $(this).val(''); 123 return; 124 } 125
126 // Validate file size (5MB) 127 if (file.size > 5242880) { 128 $('#photo-preview').html('<div class="alert alert-danger">File size exceeds 5MB limit.</div>'); 129 $('#photo-filename').html(''); 130 $('#photo-upload-btn').html('<i class="bi bi-image me-2"></i>Choose Photo'); 131 $('#photo-camera-btn').html('<i class="bi bi-camera-fill me-2"></i>Take Photo'); 132 $(this).val(''); 133 return; 134 } 135 136 // Update button text and show filename 137 $('#photo-upload-btn').html('<i class="bi bi-check-circle-fill me-2"></i>Photo Selected'); 138 $('#photo-filename').html('<i class="bi bi-file-image me-1"></i>' + file.name + ' (' + formatFileSize(file.size) + ')'); 139 140 // Show preview 141 var reader = new FileReader(); 142 reader.onload = function(e) { 143 var previewHtml = '<div class="photo-preview-container text-center">' + 144 '<img src="' + e.target.result + '" class="img-thumbnail" style="max-width: 100%; max-height: 120px; border-radius: 6px;">' + 145 '<button type="button" class="btn btn-sm btn-outline-danger mt-1" id="remove-photo">' + 146 '<i class="bi bi-trash me-1"></i>Remove' + 147 '</button>' + 148 '</div>'; 149 $('#photo-preview').html(previewHtml); 150 151 // Setup remove button 152 $('#remove-photo').on('click', function() { 153 $('#display_photo').val(''); 154 $('#photo-preview').html(''); 155 $('#photo-filename').html(''); 156 $('#photo-upload-btn').html('<i class="bi bi-image me-2"></i>Choose Photo'); 157 $('#photo-camera-btn').html('<i class="bi bi-camera-fill me-2"></i>Take Photo'); 158 }); 159 }; 160 reader.readAsDataURL(file); 161 }); 162 } 163 164 /** 165 * Setup feature toggle functionality 166 */ 167 function setupFeatureToggle() { 168 $('#toggle-more-features').on('click', function(e) { 169 e.preventDefault(); 170 var $additionalFeatures = $('#additional-features'); 171 var $icon = $(this).find('i'); 172 173 if ($additionalFeatures.is(':visible')) { 174 $additionalFeatures.slideUp(); 175 $icon.removeClass('fa-chevron-up').addClass('fa-chevron-down'); 176 $(this).html('Show More Features <i class="fa fa-chevron-down"></i>'); 177 } else { 178 $additionalFeatures.slideDown(); 179 $icon.removeClass('fa-chevron-down').addClass('fa-chevron-up'); 180 $(this).html('Show Fewer Features <i class="fa fa-chevron-up"></i>'); 181 } 182 }); 183 } 184 185 /** 186 * Setup feature chip interaction 187 * Handles visual state changes for chip-style feature buttons 188 */ 189 function setupFeatureChipInteraction() { 190 // Use event delegation for dynamically added chips 191 $(document).on('click', '.feature-chip-label', function() { 192 var $label = $(this); 193 var $checkbox = $label.prev('.feature-checkbox'); 194 195 // Toggle the checkbox state 196 $checkbox.prop('checked', !$checkbox.prop('checked')); 197 198 // Update visual state 199 updateChipVisualState($label, $checkbox.prop('checked')); 200 }); 201 202 // Initialize visual state for any pre-checked boxes 203 $('.feature-checkbox').each(function() { 204 var $checkbox = $(this); 205 var $label = $checkbox.next('.feature-chip-label'); 206 if ($checkbox.prop('checked')) { 207 updateChipVisualState($label, true); 208 } 209 }); 210 } 211 212 /** 213 * Update chip visual state 214 */ 215 function updateChipVisualState($label, isChecked) { 216 if (isChecked) { 217 $label.css({ 218 'background': '#c41e3a', 219 'border': '2px solid #c41e3a', 220 'color': 'white', 221 'font-weight': '600' 222 }); 223 } else { 224 $label.css({ 225 'background': 'white', 226 'border': '2px solid #dee2e6', 227 'color': '#212529', 228 'font-weight': 'normal' 229 }); 230 } 231 } 232 233 /** 234 * Format file size for display 235 */ 236 function formatFileSize(bytes) { 237 if (bytes === 0) return '0 Bytes'; 238 var k = 1024; 239 var sizes = ['Bytes', 'KB', 'MB']; 240 var i = Math.floor(Math.log(bytes) / Math.log(k)); 241 return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; 242 } 243 244 /** 245 * Get selected features 246 */ 247 function getSelectedFeatures() { 248 var selected = []; 249 $('.feature-checkbox:checked').each(function() { 250 selected.push($(this).val()); 251 }); 252 return selected; 253 } 254 255 // Initialize on document ready 256 $(document).ready(function() { 257 init(); 258 }); 259 260 // Expose public methods 261 window.PhotoFeatures = { 262 getSelectedFeatures: getSelectedFeatures, 263 init: init, // Expose init so it can be called when flyup opens 264 renderFeatures: renderFeatures // Expose render so features can be re-rendered 265 }; 266 267})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.