mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
live preview updates to formatting
This commit is contained in:
@@ -92,7 +92,7 @@
|
|||||||
|
|
||||||
.main-grid {
|
.main-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 450px;
|
grid-template-columns: 1fr;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
@@ -216,6 +216,8 @@
|
|||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
@@ -699,7 +701,7 @@
|
|||||||
<!-- Main Grid -->
|
<!-- Main Grid -->
|
||||||
<div class="main-grid">
|
<div class="main-grid">
|
||||||
<!-- Display Panel -->
|
<!-- Display Panel -->
|
||||||
<div class="display-panel">
|
<div class="display-panel" style="max-width: 1200px; margin: 0 auto;">
|
||||||
<h2><i class="fas fa-desktop"></i> Live Display Preview</h2>
|
<h2><i class="fas fa-desktop"></i> Live Display Preview</h2>
|
||||||
<div class="display-preview" id="displayPreview">
|
<div class="display-preview" id="displayPreview">
|
||||||
<div id="previewStage" class="preview-stage" style="display:none;">
|
<div id="previewStage" class="preview-stage" style="display:none;">
|
||||||
@@ -740,7 +742,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Control Panel -->
|
<!-- Control Panel under the preview, wider -->
|
||||||
<div class="control-panel">
|
<div class="control-panel">
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab-btn active" onclick="showTab('overview')">
|
<button class="tab-btn active" onclick="showTab('overview')">
|
||||||
@@ -1085,10 +1087,13 @@
|
|||||||
<h3>Sports Configuration</h3>
|
<h3>Sports Configuration</h3>
|
||||||
<p>Configure which sports leagues to display and their settings.</p>
|
<p>Configure which sports leagues to display and their settings.</p>
|
||||||
<!-- Sports configuration will be populated by JavaScript -->
|
<!-- Sports configuration will be populated by JavaScript -->
|
||||||
<div id="sports-config">
|
<div id="sports-config">
|
||||||
Loading sports configuration...
|
Loading sports configuration...
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success" onclick="saveSportsConfig()">Save Sports Settings</button>
|
<div style="margin-top:10px; display:flex; gap:10px;">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="refreshSportsConfig()">Refresh</button>
|
||||||
|
<button type="button" class="btn btn-success" onclick="saveSportsConfig()">Save Sports Settings</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1950,6 +1955,8 @@
|
|||||||
// Load specific data when tabs are opened
|
// Load specific data when tabs are opened
|
||||||
if (tabName === 'news') {
|
if (tabName === 'news') {
|
||||||
loadNewsManagerData();
|
loadNewsManagerData();
|
||||||
|
} else if (tabName === 'sports') {
|
||||||
|
refreshSportsConfig();
|
||||||
} else if (tabName === 'logs') {
|
} else if (tabName === 'logs') {
|
||||||
fetchLogs();
|
fetchLogs();
|
||||||
} else if (tabName === 'raw-json') {
|
} 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
|
// Utility functions
|
||||||
function showNotification(message, type) {
|
function showNotification(message, type) {
|
||||||
const notification = document.getElementById('notification');
|
const notification = document.getElementById('notification');
|
||||||
@@ -2843,9 +2868,88 @@
|
|||||||
showNotification('News status refreshed', 'success');
|
showNotification('News status refreshed', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sports configuration placeholder
|
// Sports configuration
|
||||||
function saveSportsConfig() {
|
async function refreshSportsConfig(){
|
||||||
showNotification('Sports configuration saved (placeholder)', 'success');
|
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 `
|
||||||
|
<div style="border:1px solid #ddd; border-radius:6px; padding:12px; margin:10px 0;">
|
||||||
|
<label style="display:flex; align-items:center; gap:8px;">
|
||||||
|
<input type="checkbox" data-league="${l.key}" class="sp-enabled" ${sec.enabled ? 'checked' : ''}>
|
||||||
|
<strong>${l.label}</strong>
|
||||||
|
</label>
|
||||||
|
<div class="form-row" style="margin-top:10px;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Live Priority</label>
|
||||||
|
<input type="checkbox" data-league="${l.key}" class="sp-live-priority" ${sec.live_priority ? 'checked' : ''}>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Show Odds</label>
|
||||||
|
<input type="checkbox" data-league="${l.key}" class="sp-show-odds" ${sec.show_odds ? 'checked' : ''}>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Favorites Only</label>
|
||||||
|
<input type="checkbox" data-league="${l.key}" class="sp-favorites-only" ${sec.show_favorite_teams_only ? 'checked' : ''}>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Favorite Teams</label>
|
||||||
|
<input type="text" data-league="${l.key}" class="form-control sp-favorites" value="${fav}">
|
||||||
|
<div class="description">Comma-separated abbreviations</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).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');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -201,6 +201,23 @@ def start_display():
|
|||||||
logger.info("Using fallback DisplayManager for web simulation")
|
logger.info("Using fallback DisplayManager for web simulation")
|
||||||
|
|
||||||
display_monitor.start()
|
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
|
display_running = True
|
||||||
|
|
||||||
@@ -398,10 +415,15 @@ def system_action():
|
|||||||
result = subprocess.run(['sudo', 'reboot'],
|
result = subprocess.run(['sudo', 'reboot'],
|
||||||
capture_output=True, text=True)
|
capture_output=True, text=True)
|
||||||
elif action == 'git_pull':
|
elif action == 'git_pull':
|
||||||
home_dir = str(Path.home())
|
# Run git pull from the repository directory where this file lives
|
||||||
project_dir = os.path.join(home_dir, 'LEDMatrix')
|
repo_dir = Path(__file__).resolve().parent
|
||||||
result = subprocess.run(['git', 'pull'],
|
if not (repo_dir / '.git').exists():
|
||||||
capture_output=True, text=True, cwd=project_dir, check=False)
|
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:
|
else:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
@@ -562,10 +584,14 @@ def run_action_route():
|
|||||||
result = subprocess.run(['sudo', 'reboot'],
|
result = subprocess.run(['sudo', 'reboot'],
|
||||||
capture_output=True, text=True)
|
capture_output=True, text=True)
|
||||||
elif action == 'git_pull':
|
elif action == 'git_pull':
|
||||||
home_dir = str(Path.home())
|
repo_dir = Path(__file__).resolve().parent
|
||||||
project_dir = os.path.join(home_dir, 'LEDMatrix')
|
if not (repo_dir / '.git').exists():
|
||||||
result = subprocess.run(['git', 'pull'],
|
return jsonify({
|
||||||
capture_output=True, text=True, cwd=project_dir, check=True)
|
'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:
|
else:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
@@ -847,6 +873,19 @@ def get_current_display():
|
|||||||
"""Get current display image as base64."""
|
"""Get current display image as base64."""
|
||||||
return jsonify(current_display_data)
|
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')
|
@socketio.on('connect')
|
||||||
def handle_connect():
|
def handle_connect():
|
||||||
"""Handle client connection."""
|
"""Handle client connection."""
|
||||||
|
|||||||
Reference in New Issue
Block a user