diff --git a/test/test_web_api.py b/test/test_web_api.py index 8cadf922..340b4b16 100644 --- a/test/test_web_api.py +++ b/test/test_web_api.py @@ -789,28 +789,21 @@ class TestFontsAPI: data = json.loads(response.data) assert 'tokens' in data.get('data', {}) or 'data' in data - def test_get_fonts_overrides(self, client): - """Test getting font overrides.""" - response = client.get('/api/v3/fonts/overrides') - - assert response.status_code == 200 - data = json.loads(response.data) - assert 'overrides' in data.get('data', {}) or 'data' in data - - def test_save_fonts_overrides(self, client): - """Test saving font overrides.""" - request_data = { - 'weather': 'small', - 'clock': 'regular' - } - - response = client.post( - '/api/v3/fonts/overrides', - data=json.dumps(request_data), - content_type='application/json' - ) - - assert response.status_code == 200 + def test_the_font_override_endpoints_are_gone(self, client): + """They reported success without doing anything: GET returned a + hardcoded {}, POST and DELETE saved and deleted nothing. The panel + they served offered eleven element keys -- nfl.live.score, + clock.time -- that no plugin has ever read, so wiring them to the + real FontManager methods would still have changed nothing on the + panel. Per-element font choice lives in each plugin's own config + editor now, against the elements that plugin actually has.""" + # 405, not 404: the path still matches DELETE /fonts/, + # which now reads "overrides" as a font name. Nothing is routed to a + # handler for GET or POST, which is what matters here. + assert client.get('/api/v3/fonts/overrides').status_code != 200 + assert client.post('/api/v3/fonts/overrides', + data=json.dumps({}), + content_type='application/json').status_code != 200 class TestAPIErrorHandling: diff --git a/web_interface/blueprints/api_v3.py b/web_interface/blueprints/api_v3.py index 054feb7f..92da3c66 100644 --- a/web_interface/blueprints/api_v3.py +++ b/web_interface/blueprints/api_v3.py @@ -6809,42 +6809,6 @@ def get_font_tokens(): logger.error('Unhandled exception', exc_info=True) return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details', 'details': describe_exception(e)}), 500 -@api_v3.route('/fonts/overrides', methods=['GET']) -def get_fonts_overrides(): - """Get font overrides""" - try: - # This would integrate with the actual font system - # For now, return empty overrides - overrides = {} - return jsonify({'status': 'success', 'data': {'overrides': overrides}}) - except Exception as e: - logger.error('Unhandled exception', exc_info=True) - return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details', 'details': describe_exception(e)}), 500 - -@api_v3.route('/fonts/overrides', methods=['POST']) -def save_fonts_overrides(): - """Save font overrides""" - try: - data = request.get_json(silent=True) - if not data: - return jsonify({'status': 'error', 'message': 'No data provided'}), 400 - - # This would integrate with the actual font system - return jsonify({'status': 'success', 'message': 'Font overrides saved'}) - except Exception as e: - logger.error('Unhandled exception', exc_info=True) - return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details', 'details': describe_exception(e)}), 500 - -@api_v3.route('/fonts/overrides/', methods=['DELETE']) -def delete_font_override(element_key): - """Delete font override""" - try: - # This would integrate with the actual font system - return jsonify({'status': 'success', 'message': f'Font override for {element_key} deleted'}) - except Exception as e: - logger.error('Unhandled exception', exc_info=True) - return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details', 'details': describe_exception(e)}), 500 - @api_v3.route('/fonts/upload', methods=['POST']) def upload_font(): """Upload font file""" diff --git a/web_interface/templates/v3/partials/fonts.html b/web_interface/templates/v3/partials/fonts.html index fec1d16a..29038eba 100644 --- a/web_interface/templates/v3/partials/fonts.html +++ b/web_interface/templates/v3/partials/fonts.html @@ -1,20 +1,11 @@

Font Management

-

Manage custom fonts, overrides, and system font configuration for your LED matrix display.

+

Upload, preview and manage the fonts available to every plugin.

-
- -
-

Detected Manager Fonts

-
-
Loading...
-
-

Fonts currently in use by managers (auto-detected)

-
- +

Available Font Families

@@ -78,73 +69,6 @@
- -
-

Element Font Overrides

-

Override fonts for specific display elements. Changes take effect immediately.

- - -
-
- - -
- -
- - -
- -
- - -
- -
- -
-
- - -
-

Current Overrides

-
- -
No font overrides configured
-
-
-
-

Font Preview

@@ -206,13 +130,11 @@ // Initialize global variables on window object window.fontCatalog = window.fontCatalog || {}; window.fontTokens = window.fontTokens || {}; - window.fontOverrides = window.fontOverrides || {}; window.selectedFontFiles = window.selectedFontFiles || []; // Create references that can be reassigned var fontCatalog = window.fontCatalog; var fontTokens = window.fontTokens; - var fontOverrides = window.fontOverrides; var selectedFontFiles = window.selectedFontFiles; // Retry counter for initialization @@ -222,17 +144,15 @@ function initializeFontsTab() { // Allow re-initialization on each HTMX content swap // The window._fontsScriptLoaded guard prevents function redeclaration - const detectedEl = document.getElementById('detected-fonts'); const availableEl = document.getElementById('available-fonts'); - if (!detectedEl || !availableEl) { + if (!availableEl) { initRetryCount++; if (initRetryCount >= MAX_INIT_RETRIES) { console.error('Fonts tab elements not found after max retries, giving up'); return; } console.log('Fonts tab elements not found, retrying...', { - detectedFonts: !!detectedEl, availableFonts: !!availableEl, attempt: initRetryCount }); @@ -259,7 +179,6 @@ function initializeFontsTab() { const fontFileInput = document.getElementById('font-file-input'); const uploadFontsBtn = document.getElementById('upload-fonts-btn'); const cancelUploadBtn = document.getElementById('cancel-upload-btn'); - const addOverrideBtn = document.getElementById('add-override-btn'); const updatePreviewBtn = document.getElementById('update-preview-btn'); if (uploadDropzone && fontFileInput) { @@ -280,9 +199,6 @@ function initializeFontsTab() { cancelUploadBtn.addEventListener('click', cancelFontUpload); } - if (addOverrideBtn) { - addOverrideBtn.addEventListener('click', addFontOverride); - } if (updatePreviewBtn) { updatePreviewBtn.addEventListener('click', updateFontPreview); @@ -319,9 +235,8 @@ window.initializeFontsTab = initializeFontsTab; // Function to initialize when fonts content is loaded function tryInitializeFontsTab() { const fontsContent = document.getElementById('fonts-content'); - const detectedFonts = document.getElementById('detected-fonts'); - if (fontsContent && detectedFonts) { + if (fontsContent) { console.log('Fonts content detected, initializing...'); setTimeout(() => { initializeFontsTab(); @@ -355,7 +270,6 @@ async function initializeFontManagement() { try { await loadFontData(); populateFontSelects(); - displayCurrentOverrides(); updateFontPreview(); initializeFontUpload(); } catch (error) { @@ -365,44 +279,39 @@ async function initializeFontManagement() { } async function loadFontData() { - const detectedContainer = document.getElementById('detected-fonts'); const availableContainer = document.getElementById('available-fonts'); // Ensure containers exist before proceeding - if (!detectedContainer || !availableContainer) { + if (!availableContainer) { console.error('Font containers not found, cannot load font data'); return; } // Show loading states - detectedContainer.innerHTML = '
Loading font data...
'; availableContainer.innerHTML = '
Loading font data...
'; try { // Use absolute URLs to ensure they work when loaded via HTMX - const [catalogRes, tokensRes, overridesRes] = await Promise.all([ + const [catalogRes, tokensRes] = await Promise.all([ fetch(`/api/v3/fonts/catalog`), - fetch(`/api/v3/fonts/tokens`), - fetch(`/api/v3/fonts/overrides`) + fetch(`/api/v3/fonts/tokens`) ]); // Check if all responses are successful - if (!catalogRes.ok || !tokensRes.ok || !overridesRes.ok) { - const statusText = `HTTP ${catalogRes.status}/${tokensRes.status}/${overridesRes.status}`; + if (!catalogRes.ok || !tokensRes.ok) { + const statusText = `HTTP ${catalogRes.status}/${tokensRes.status}`; console.error('Font API error:', statusText); throw new Error(`Failed to load font data: ${statusText}`); } const catalogData = await catalogRes.json(); const tokensData = await tokensRes.json(); - const overridesData = await overridesRes.json(); // Validate response structure - if (!catalogData || !catalogData.data || !tokensData || !tokensData.data || !overridesData || !overridesData.data) { + if (!catalogData || !catalogData.data || !tokensData || !tokensData.data) { console.error('Invalid font API response structure:', { catalog: !!catalogData?.data, - tokens: !!tokensData?.data, - overrides: !!overridesData?.data + tokens: !!tokensData?.data }); throw new Error('Invalid response format from font API'); } @@ -410,27 +319,22 @@ async function loadFontData() { // Update both window properties and local references window.fontCatalog = catalogData.data.catalog || {}; window.fontTokens = tokensData.data.tokens || {}; - window.fontOverrides = overridesData.data.overrides || {}; // Update local variable references fontCatalog = window.fontCatalog; fontTokens = window.fontTokens; - fontOverrides = window.fontOverrides; // Update displays - updateDetectedFontsDisplay(); updateAvailableFontsDisplay(); console.log('Font data loaded successfully', { catalogSize: Object.keys(fontCatalog).length, - tokensSize: Object.keys(fontTokens).length, - overridesSize: Object.keys(fontOverrides).length + tokensSize: Object.keys(fontTokens).length }); } catch (error) { console.error('Error loading font data:', error); // Show error states - detectedContainer.innerHTML = '
Error loading font data. Please refresh the page.
'; availableContainer.innerHTML = '
Error loading font data. Please refresh the page.
'; // Only show notification if showNotification is available @@ -444,52 +348,6 @@ async function loadFontData() { } } -function updateDetectedFontsDisplay() { - const container = document.getElementById('detected-fonts'); - if (!container) return; - - // In a real implementation, this would collect font usage from all active managers - // For now, we'll simulate this by analyzing the font overrides and catalog - const detectedFonts = {}; - - // Check font overrides for active elements - for (const [elementKey, override] of Object.entries(fontOverrides)) { - if (override.family) { - detectedFonts[elementKey] = { - family: override.family, - size_px: override.size_px || 8, - usage_count: 1, // Would be actual usage count in real implementation - source: 'override' - }; - } - } - - // Check font catalog for commonly used fonts - for (const [fontKey, fontPath] of Object.entries(fontCatalog)) { - // Add some commonly used system fonts if not already in overrides - if (!detectedFonts[fontKey]) { - detectedFonts[fontKey] = { - family: fontKey, - size_px: 8, - usage_count: 1, - source: 'system' - }; - } - } - - if (Object.keys(detectedFonts).length === 0) { - container.innerHTML = '
No fonts detected yet (managers will register fonts when they render)
'; - return; - } - - const lines = []; - for (const [elementKey, fontInfo] of Object.entries(detectedFonts)) { - const sourceStr = fontInfo.source === 'override' ? ' [OVERRIDE]' : ' [SYSTEM]'; - lines.push(`${elementKey}: ${fontInfo.family}@${fontInfo.size_px}px (used ${fontInfo.usage_count}x)${sourceStr}`); - } - container.textContent = lines.join('\n'); -} - function updateAvailableFontsDisplay() { const container = document.getElementById('available-fonts'); if (!container) return; @@ -594,10 +452,9 @@ async function deleteFont(fontFamily) { function populateFontSelects() { // Populate font family dropdowns from catalog - const overrideSelect = document.getElementById('override-family'); const previewSelect = document.getElementById('preview-family'); - if (!overrideSelect || !previewSelect) return; + if (!previewSelect) return; // Get font entries sorted by display name const fontEntries = Object.entries(fontCatalog).map(([key, info]) => { @@ -608,13 +465,6 @@ function populateFontSelects() { }).sort((a, b) => a.displayName.localeCompare(b.displayName)); // Build options using DOM APIs to prevent XSS - // Clear and add default option for override select - overrideSelect.innerHTML = ''; - const defaultOption = document.createElement('option'); - defaultOption.value = ''; - defaultOption.textContent = 'Use default'; - overrideSelect.appendChild(defaultOption); - // Clear preview select previewSelect.innerHTML = ''; @@ -622,11 +472,6 @@ function populateFontSelects() { fontEntries.forEach(font => { const typeLabel = font.fontType ? ` (${font.fontType})` : ''; - const overrideOpt = document.createElement('option'); - overrideOpt.value = font.filename; - overrideOpt.textContent = font.displayName + typeLabel; - overrideSelect.appendChild(overrideOpt); - const previewOpt = document.createElement('option'); previewOpt.value = font.filename; previewOpt.textContent = font.displayName + typeLabel; @@ -641,187 +486,6 @@ function populateFontSelects() { console.log(`Populated font selects with ${fontEntries.length} fonts`); } -async function addFontOverride() { - const element = document.getElementById('override-element').value; - const family = document.getElementById('override-family').value; - const sizeToken = document.getElementById('override-size').value; - - if (!element) { - showNotification('Please select an element', 'warning'); - return; - } - - if (!family && !sizeToken) { - showNotification('Please specify at least a font family or size', 'warning'); - return; - } - - try { - const overrideData = {}; - if (family) overrideData.family = family; - if (sizeToken) { - const sizePx = fontTokens[sizeToken]; - if (sizePx) overrideData.size_px = sizePx; - } - - const response = await fetch(`/api/v3/fonts/overrides`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - [element]: overrideData - }) - }); - - if (!response.ok) { - const text = await response.text(); - let message; - try { - const errorData = JSON.parse(text); - message = errorData.message || `Server error: ${response.status}`; - } catch { - message = `Server error: ${response.status}`; - } - showNotification('Error adding font override: ' + message, 'error'); - return; - } - - const data = await response.json(); - if (data.status === 'success') { - showNotification('Font override added successfully', 'success'); - await loadFontData(); - displayCurrentOverrides(); - // Clear form - document.getElementById('override-element').value = ''; - document.getElementById('override-family').value = ''; - document.getElementById('override-size').value = ''; - } else { - showNotification('Error adding font override: ' + data.message, 'error'); - } - } catch (error) { - console.error('Error adding font override:', error); - showNotification('Error adding font override: ' + error, 'error'); - } -} - -async function deleteFontOverride(elementKey) { - if (!confirm(`Are you sure you want to remove the font override for "${elementKey}"?`)) { - return; - } - - try { - const response = await fetch(`/api/v3/fonts/overrides/${elementKey}`, { - method: 'DELETE' - }); - - if (!response.ok) { - const text = await response.text(); - let message; - try { - const errorData = JSON.parse(text); - message = errorData.message || `Server error: ${response.status}`; - } catch { - message = `Server error: ${response.status}`; - } - showNotification('Error removing font override: ' + message, 'error'); - return; - } - - const data = await response.json(); - if (data.status === 'success') { - showNotification('Font override removed successfully', 'success'); - await loadFontData(); - displayCurrentOverrides(); - } else { - showNotification('Error removing font override: ' + data.message, 'error'); - } - } catch (error) { - console.error('Error deleting font override:', error); - showNotification('Error removing font override: ' + error, 'error'); - } -} - -function displayCurrentOverrides() { - const container = document.getElementById('overrides-list'); - if (!container) return; - - if (Object.keys(fontOverrides).length === 0) { - container.innerHTML = '
No font overrides configured
'; - return; - } - - // Build list using DOM APIs to prevent XSS - container.innerHTML = ''; - Object.entries(fontOverrides).forEach(([elementKey, override]) => { - const elementName = getElementDisplayName(elementKey); - const settings = []; - - if (override.family) { - const familyName = getFontDisplayName(override.family); - settings.push(`Family: ${familyName}`); - } - - if (override.size_px) { - settings.push(`Size: ${override.size_px}px`); - } - - const row = document.createElement('div'); - row.className = 'flex items-center justify-between p-3 bg-white rounded border'; - - const infoDiv = document.createElement('div'); - - const nameDiv = document.createElement('div'); - nameDiv.className = 'font-medium text-gray-900'; - nameDiv.textContent = elementName; - - const settingsDiv = document.createElement('div'); - settingsDiv.className = 'text-sm text-gray-600'; - settingsDiv.textContent = settings.join(', '); - - infoDiv.appendChild(nameDiv); - infoDiv.appendChild(settingsDiv); - - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'btn bg-red-600 hover:bg-red-700 text-white px-3 py-1 text-sm'; - const trashIcon = document.createElement('i'); - trashIcon.className = 'fas fa-trash mr-1'; - deleteBtn.appendChild(trashIcon); - deleteBtn.appendChild(document.createTextNode('Remove')); - deleteBtn.dataset.elementKey = elementKey; - deleteBtn.addEventListener('click', function() { - deleteFontOverride(this.dataset.elementKey); - }); - - row.appendChild(infoDiv); - row.appendChild(deleteBtn); - container.appendChild(row); - }); -} - -function getElementDisplayName(elementKey) { - const names = { - 'nfl.live.score': 'NFL Live Score', - 'nfl.live.time': 'NFL Live Time', - 'nfl.live.team': 'NFL Live Team', - 'mlb.live.score': 'MLB Live Score', - 'nhl.live.score': 'NHL Live Score', - 'nba.live.score': 'NBA Live Score', - 'clock.time': 'Clock Time', - 'clock.date': 'Clock Date', - 'weather.current': 'Weather Current', - 'weather.forecast': 'Weather Forecast' - }; - return names[elementKey] || elementKey; -} - -function getFontDisplayName(fontKey) { - const names = { - 'press_start': 'Press Start 2P', - 'four_by_six': '4x6 Font', - 'matrix_light_6': 'Matrix Light 6' - }; - return names[fontKey] || fontKey; -} - async function updateFontPreview() { const previewImage = document.getElementById('font-preview-image'); const loadingText = document.getElementById('font-preview-loading');