Editor Support

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Jason
2026-05-05 15:42:30 -05:00
parent 9f7c078ccc
commit 29154bd651
32 changed files with 3698 additions and 17 deletions
+18
View File
@@ -300,6 +300,24 @@ body {
z-index: 4;
}
.layer-foreground {
z-index: 5;
pointer-events: none; /* Allow clicks to pass through to controls below */
}
/* Layer transform modifiers */
.layer-flip-horizontal {
transform: scaleX(-1);
}
.layer-flip-vertical {
transform: scaleY(-1);
}
.layer-flip-both {
transform: scale(-1, -1);
}
.config-preview-notice {
position: absolute;
bottom: 10px;
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+144 -11
View File
@@ -106,7 +106,13 @@ let history = ['start'];
function initFromURL() {
const params = new URLSearchParams(window.location.search);
// Priority 1: Direct product view
// Priority 0: Direct product from Flask route (e.g., /quiz/product/404)
if (window.INITIAL_PRODUCT && window.INITIAL_PRODUCT !== '') {
showProductByCode(window.INITIAL_PRODUCT);
return;
}
// Priority 1: Direct product view from query param
const productCode = params.get('p');
if (productCode) {
accumulatedBitValue = parseInt(params.get('b') || '0', 10);
@@ -705,6 +711,7 @@ function showProductDetail(product) {
// Get product image configuration
const productImage = product.image || `https://via.placeholder.com/400x400/e0e0e0/666?text=${encodeURIComponent(prodCode)}`;
const useCanvasAPI = product.imageConfig && product.imageConfig.useCanvasAPI === true;
const hasLayeredImages = product.imageConfig && product.imageConfig.layered === true;
const hasFlatImages = product.flatImages === true || product.imagePattern;
const useDynamicAPI = product.useDynamicAPI === true;
@@ -795,20 +802,21 @@ function showProductDetail(product) {
const contentDiv = document.getElementById('content');
// Build the image display (layered, flat, or dynamic API)
// Build the image display (Canvas API, layered, flat, or dynamic API)
let imageDisplayHtml = '';
if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) {
// Use layered image system
const basePath = product.imageConfig.basePath || '';
const layers = product.imageConfig.layers || {};
if (useCanvasAPI || (hasLayeredImages && !hasFlatImages && !useDynamicAPI)) {
// Use layered image system (Canvas API or static paths)
const basePath = product.imageConfig?.basePath || '';
const layers = product.imageConfig?.layers || {};
imageDisplayHtml = `
<div class="door-configurator" id="door-configurator">
${layers.base ? `<img src="${basePath}${layers.base}" class="layer-base" alt="Base">` : ''}
<img src="" class="layer-base" alt="Base">
<img src="" class="layer-door" id="door-layer" alt="Door" style="display:none;">
${layers.hardware ? `<img src="${basePath}${layers.hardware}" class="layer-hardware" id="hardware-layer" alt="Hardware">` : ''}
<img src="" class="layer-hardware" id="hardware-layer" alt="Hardware" style="display:none;">
<img src="" class="layer-overlay" id="overlay-layer" alt="View" style="display:none;">
<img src="" class="layer-foreground" alt="Foreground" style="display:none;">
<div class="config-preview-notice" id="config-notice" style="display:none;">
⚠️ Preview not available for this configuration
</div>
@@ -958,7 +966,11 @@ function showProductDetail(product) {
updateBreadcrumb();
// Initialize images based on type
if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) {
if (useCanvasAPI) {
// Use Canvas API with hierarchical fallback
updateCanvasAPIPreview(prodCode);
} else if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) {
// Use static layered images
updateProductPreview(prodCode);
} else if (hasFlatImages || useDynamicAPI) {
// Flat images or dynamic API - will update on dropdown change
@@ -969,18 +981,138 @@ function showProductDetail(product) {
}
}
// ============================================================================
// Canvas API Functions - Hierarchical Image Fallback System
// ============================================================================
/**
* Build Canvas API URL for a specific layer
* @param {string} productCode - Product code
* @param {string} layer - Layer name (base, door, hardware, overlay, foreground)
* @param {string} color - Optional color
* @param {string} material - Optional material
* @returns {string} Canvas API URL
*/
function buildCanvasAPIUrl(productCode, layer, color = null, material = null) {
let url = `${BASE_URL}/api/canvas/${productCode}/${layer}`;
const params = new URLSearchParams();
if (color) params.append('color', color.toLowerCase());
if (material) params.append('material', material.toLowerCase());
const queryString = params.toString();
return queryString ? `${url}?${queryString}` : url;
}
/**
* Update layered image preview using Canvas API
* @param {string} productCode - Product code
*/
function updateCanvasAPIPreview(productCode) {
const product = productData.find(p => p.productCode === productCode || p.id === productCode);
if (!product) return;
// Get selected values
const selectedColor = document.getElementById('config-color')?.value || '';
const selectedMaterial = document.getElementById('config-material')?.value || '';
// Get layer transform configuration (for flipping, etc.)
const layerTransforms = product.imageConfig?.layerTransforms || {};
// Helper function to apply transform classes
function applyTransform(element, layerName) {
if (!element) return;
// Remove any existing transform classes
element.classList.remove('layer-flip-horizontal', 'layer-flip-vertical', 'layer-flip-both');
// Apply new transform if configured
const transform = layerTransforms[layerName];
if (transform) {
element.classList.add(`layer-${transform}`);
}
}
// Update each layer
const baseLayer = document.querySelector('.layer-base');
const doorLayer = document.getElementById('door-layer');
const hardwareLayer = document.getElementById('hardware-layer');
const overlayLayer = document.getElementById('overlay-layer');
const foregroundLayer = document.querySelector('.layer-foreground');
// Base layer (no color variation)
if (baseLayer && baseLayer.tagName === 'IMG') {
baseLayer.src = buildCanvasAPIUrl(productCode, 'base');
applyTransform(baseLayer, 'base');
baseLayer.onerror = function() {
console.warn('Base layer not found via Canvas API');
};
}
// Door layer (with color)
if (doorLayer) {
applyTransform(doorLayer, 'door');
if (selectedColor) {
doorLayer.src = buildCanvasAPIUrl(productCode, 'door', selectedColor);
doorLayer.style.display = 'block';
doorLayer.onerror = function() {
console.warn(`Door layer not found for color: ${selectedColor}`);
this.style.display = 'none';
showConfigNotice(true);
};
doorLayer.onload = function() {
showConfigNotice(false);
};
} else {
doorLayer.style.display = 'none';
}
}
// Hardware layer
if (hardwareLayer) {
hardwareLayer.src = buildCanvasAPIUrl(productCode, 'hardware');
applyTransform(hardwareLayer, 'hardware');
hardwareLayer.onerror = function() {
console.warn('Hardware layer not found via Canvas API');
this.style.display = 'none';
};
hardwareLayer.onload = function() {
this.style.display = 'block';
};
}
// Foreground layer
if (foregroundLayer && foregroundLayer.tagName === 'IMG') {
foregroundLayer.src = buildCanvasAPIUrl(productCode, 'foreground');
applyTransform(foregroundLayer, 'foreground');
foregroundLayer.onerror = function() {
console.warn('Foreground layer not found via Canvas API');
// This is fine - foreground is optional
};
}
}
// ============================================================================
// Update product preview based on configuration
// ============================================================================
function updateProductPreview(productCode) {
console.log('updateProductPreview called with:', productCode);
const product = productData.find(p => p.productCode === productCode || p.id === productCode);
if (!product) return;
// Check which system to use
// Check which system to use - Canvas API has priority
const useCanvasAPI = product.imageConfig && product.imageConfig.useCanvasAPI === true;
const hasFlatImages = product.flatImages === true || product.imagePattern;
const useDynamicAPI = product.useDynamicAPI === true;
console.log('Product flags - hasFlatImages:', hasFlatImages, 'useDynamicAPI:', useDynamicAPI);
console.log('Product flags - useCanvasAPI:', useCanvasAPI, 'hasFlatImages:', hasFlatImages, 'useDynamicAPI:', useDynamicAPI);
// Priority: Canvas API > Flat Images > Dynamic API > Layered Static
if (useCanvasAPI) {
// Use Canvas API with hierarchical fallback
updateCanvasAPIPreview(productCode);
return;
}
if (hasFlatImages) {
// Use pre-rendered flat images
@@ -1810,6 +1942,7 @@ startOver = function() {
// Initialize search bar when page loads
window.addEventListener('DOMContentLoaded', function() {
init(); // Load quiz data and initialize
initSearchBar();
toggleSearchBar();
});