From 95e3e4dda4eb7bc2aefa40d4818006a60f8164db Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Sun, 10 Aug 2025 12:31:12 -0500 Subject: [PATCH] live preview updates to formatting --- templates/index_v2.html | 124 ++++++++++++++++++++++++++++++++++++---- web_interface_v2.py | 55 +++++++++++++++--- 2 files changed, 161 insertions(+), 18 deletions(-) diff --git a/templates/index_v2.html b/templates/index_v2.html index a188c838..d0479363 100644 --- a/templates/index_v2.html +++ b/templates/index_v2.html @@ -92,7 +92,7 @@ .main-grid { display: grid; - grid-template-columns: 1fr 450px; + grid-template-columns: 1fr; gap: 20px; margin-bottom: 20px; } @@ -216,6 +216,8 @@ border-radius: var(--border-radius); padding: 20px; box-shadow: var(--shadow); + max-width: 1200px; + margin: 0 auto; } .tabs { @@ -699,7 +701,7 @@
-
+

Live Display Preview

- +
+
+ Loading sports configuration... +
+
+ + +
@@ -1950,6 +1955,8 @@ // Load specific data when tabs are opened if (tabName === 'news') { loadNewsManagerData(); + } else if (tabName === 'sports') { + refreshSportsConfig(); } else if (tabName === 'logs') { fetchLogs(); } else if (tabName === 'raw-json') { @@ -2215,6 +2222,24 @@ } } + async function loadLayout(){ + try { + const res = await fetch('/api/editor/layouts'); + const data = await res.json(); + if (data.status === 'success' && data.data && data.data.elements) { + currentElements = data.data.elements; + selectedElement = null; + updatePreview(); + updatePropertiesPanel(); + showNotification('Layout loaded', 'success'); + } else { + showNotification('No saved layout found', 'warning'); + } + } catch (err) { + showNotification('Error loading layout: ' + err, 'error'); + } + } + // Utility functions function showNotification(message, type) { const notification = document.getElementById('notification'); @@ -2843,9 +2868,88 @@ showNotification('News status refreshed', 'success'); } - // Sports configuration placeholder - function saveSportsConfig() { - showNotification('Sports configuration saved (placeholder)', 'success'); + // Sports configuration + async function refreshSportsConfig(){ + try { + const res = await fetch('/api/system/status'); + const stats = await res.json(); + // Build a minimal sports UI off current config + const cfg = currentConfig; + const leagues = [ + { key: 'nfl_scoreboard', label: 'NFL' }, + { key: 'mlb', label: 'MLB' }, + { key: 'milb', label: 'MiLB' }, + { key: 'nhl_scoreboard', label: 'NHL' }, + { key: 'nba_scoreboard', label: 'NBA' }, + { key: 'ncaa_fb_scoreboard', label: 'NCAA FB' }, + { key: 'ncaa_baseball_scoreboard', label: 'NCAA Baseball' }, + { key: 'ncaam_basketball_scoreboard', label: 'NCAAM Basketball' }, + { key: 'soccer_scoreboard', label: 'Soccer' } + ]; + const container = document.getElementById('sports-config'); + const html = leagues.map(l => { + const sec = cfg[l.key] || {}; + const fav = (sec.favorite_teams || []).join(', '); + return ` +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
Comma-separated abbreviations
+
+
+
+ `; + }).join(''); + container.innerHTML = html || 'No sports configuration found.'; + } catch (err) { + document.getElementById('sports-config').textContent = 'Failed to load sports configuration'; + } + } + + async function saveSportsConfig(){ + try { + const leagues = document.querySelectorAll('.sp-enabled'); + const fragment = {}; + leagues.forEach(chk => { + const key = chk.getAttribute('data-league'); + const enabled = chk.checked; + const livePriority = document.querySelector(`.sp-live-priority[data-league="${key}"]`)?.checked || false; + const showOdds = document.querySelector(`.sp-show-odds[data-league="${key}"]`)?.checked || false; + const favoritesOnly = document.querySelector(`.sp-favorites-only[data-league="${key}"]`)?.checked || false; + const favs = document.querySelector(`.sp-favorites[data-league="${key}"]`)?.value || ''; + const favorite_teams = favs.split(',').map(s => s.trim()).filter(Boolean); + fragment[key] = { + enabled, + live_priority: livePriority, + show_odds: showOdds, + show_favorite_teams_only: favoritesOnly, + favorite_teams + }; + }); + await saveConfigJson(fragment); + } catch (err) { + showNotification('Error saving sports configuration: ' + err, 'error'); + return; + } + showNotification('Sports configuration saved', 'success'); } diff --git a/web_interface_v2.py b/web_interface_v2.py index 2e42de05..61adb73d 100644 --- a/web_interface_v2.py +++ b/web_interface_v2.py @@ -201,6 +201,23 @@ def start_display(): logger.info("Using fallback DisplayManager for web simulation") display_monitor.start() + # Immediately publish a snapshot for the client + try: + img_buffer = io.BytesIO() + display_manager.image.save(img_buffer, format='PNG') + img_str = base64.b64encode(img_buffer.getvalue()).decode() + snapshot = { + 'image': img_str, + 'width': display_manager.width, + 'height': display_manager.height, + 'timestamp': time.time() + } + # Update global and notify clients + global current_display_data + current_display_data = snapshot + socketio.emit('display_update', snapshot) + except Exception as snap_err: + logger.error(f"Failed to publish initial snapshot: {snap_err}") display_running = True @@ -398,10 +415,15 @@ def system_action(): result = subprocess.run(['sudo', 'reboot'], capture_output=True, text=True) elif action == 'git_pull': - home_dir = str(Path.home()) - project_dir = os.path.join(home_dir, 'LEDMatrix') - result = subprocess.run(['git', 'pull'], - capture_output=True, text=True, cwd=project_dir, check=False) + # Run git pull from the repository directory where this file lives + repo_dir = Path(__file__).resolve().parent + if not (repo_dir / '.git').exists(): + return jsonify({ + 'status': 'error', + 'message': f'Not a git repository: {repo_dir}' + }), 400 + result = subprocess.run(['git', 'pull'], + capture_output=True, text=True, cwd=str(repo_dir), check=False) else: return jsonify({ 'status': 'error', @@ -562,10 +584,14 @@ def run_action_route(): result = subprocess.run(['sudo', 'reboot'], capture_output=True, text=True) elif action == 'git_pull': - home_dir = str(Path.home()) - project_dir = os.path.join(home_dir, 'LEDMatrix') - result = subprocess.run(['git', 'pull'], - capture_output=True, text=True, cwd=project_dir, check=True) + repo_dir = Path(__file__).resolve().parent + if not (repo_dir / '.git').exists(): + return jsonify({ + 'status': 'error', + 'message': f'Not a git repository: {repo_dir}' + }), 400 + result = subprocess.run(['git', 'pull'], + capture_output=True, text=True, cwd=str(repo_dir), check=False) else: return jsonify({ 'status': 'error', @@ -847,6 +873,19 @@ def get_current_display(): """Get current display image as base64.""" return jsonify(current_display_data) +@app.route('/api/editor/layouts', methods=['GET']) +def get_custom_layouts(): + """Return saved custom layouts for the editor if available.""" + try: + layouts_path = Path('config') / 'custom_layouts.json' + if not layouts_path.exists(): + return jsonify({'status': 'success', 'data': {'elements': []}}) + with open(layouts_path, 'r') as f: + data = json.load(f) + return jsonify({'status': 'success', 'data': data}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}), 500 + @socketio.on('connect') def handle_connect(): """Handle client connection."""