mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-11 01:26:37 +00:00
Merge commit 'b9416ef803028ec37e0f2f756c051e52d52622d7' into claude/hdpi-scroll-performance-antialiasing-4ae609
# Conflicts: # src/vegas_mode/config.py # src/vegas_mode/coordinator.py
This commit is contained in:
@@ -121,11 +121,7 @@
|
||||
let inspectedFile = null;
|
||||
|
||||
function notify(message, kind) {
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification(message, kind || 'info');
|
||||
} else {
|
||||
console.log('[backup]', kind || 'info', message);
|
||||
}
|
||||
showNotification(message, kind || 'info');
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
@@ -136,11 +132,7 @@
|
||||
return size.toFixed(i === 0 ? 0 : 1) + ' ' + units[i];
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value == null ? '' : value).replace(/[&<>"']/g, function (c) {
|
||||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
||||
});
|
||||
}
|
||||
function escapeHtml(value) { return window.LEDEscape.html(value); }
|
||||
|
||||
async function loadPreview() {
|
||||
const el = document.getElementById('export-preview');
|
||||
|
||||
@@ -55,13 +55,12 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Initialize cache management when partial loads
|
||||
// Scoped: this script runs at global scope after every HTMX swap, and the Logs
|
||||
// partial has helpers with the same names (showError, escapeHtml). Only
|
||||
// deleteCacheFile, which the row buttons call from onclick, is exported.
|
||||
(function() {
|
||||
loadCacheFiles();
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('refresh-cache-btn').addEventListener('click', loadCacheFiles);
|
||||
})();
|
||||
loadCacheFiles();
|
||||
document.getElementById('refresh-cache-btn').addEventListener('click', loadCacheFiles);
|
||||
|
||||
function loadCacheFiles() {
|
||||
const tbody = document.getElementById('cache-files-tbody');
|
||||
@@ -190,21 +189,15 @@ function deleteCacheFile(key) {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification(data.message || 'Cache file deleted successfully', 'success');
|
||||
}
|
||||
showNotification(data.message || 'Cache file deleted successfully', 'success');
|
||||
// Reload cache files list
|
||||
loadCacheFiles();
|
||||
} else {
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification(data.message || 'Failed to delete cache file', 'error');
|
||||
}
|
||||
showNotification(data.message || 'Failed to delete cache file', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification('Error deleting cache file: ' + error.message, 'error');
|
||||
}
|
||||
showNotification('Error deleting cache file: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,17 +213,8 @@ function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
}
|
||||
|
||||
// Utility function to escape HTML
|
||||
// Quotes too: the result is interpolated into quoted attribute values, and the
|
||||
// textContent/innerHTML round-trip only escapes &, < and >.
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
function escapeHtml(text) { return window.LEDEscape.html(text); }
|
||||
|
||||
// Make deleteCacheFile available globally for onclick handlers
|
||||
window.deleteCacheFile = deleteCacheFile;
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
hx-ext="json-enc"
|
||||
hx-headers='{"Content-Type": "application/json"}'
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="showDisplaySaveResult(event.detail.xhr)"
|
||||
hx-on:htmx:after-request="showSaveResult(event.detail.xhr, 'Display settings saved', 'Display settings were not saved. Check your connection and try again.')"
|
||||
class="space-y-6"
|
||||
novalidate
|
||||
onsubmit="fixInvalidNumberInputs(this); return true;">
|
||||
@@ -863,62 +863,12 @@ document.getElementById('brightness').addEventListener('input', function() {
|
||||
document.getElementById('brightness-display').textContent = this.value;
|
||||
});
|
||||
|
||||
// Fix invalid number inputs function (if not already defined globally)
|
||||
if (typeof window.fixInvalidNumberInputs !== 'function') {
|
||||
window.fixInvalidNumberInputs = function(form) {
|
||||
if (!form) return;
|
||||
const allInputs = form.querySelectorAll('input[type="number"]');
|
||||
allInputs.forEach(input => {
|
||||
const min = parseFloat(input.getAttribute('min'));
|
||||
const max = parseFloat(input.getAttribute('max'));
|
||||
const value = parseFloat(input.value);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
if (!isNaN(min) && value < min) {
|
||||
input.value = min;
|
||||
} else if (!isNaN(max) && value > max) {
|
||||
input.value = max;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Report the outcome of a display-settings save. XMLHttpRequest has no
|
||||
// `responseJSON` (that's a jQuery property) — read `responseText` and the real
|
||||
// status code, otherwise a failed save reports success.
|
||||
window.showDisplaySaveResult = function(xhr) {
|
||||
// Only 2xx counts as saved. A network failure reports status 0, which any
|
||||
// `>= 400` test would wave through as success.
|
||||
const httpSuccess = xhr.status >= 200 && xhr.status < 300;
|
||||
let message = httpSuccess
|
||||
? 'Display settings saved'
|
||||
: 'Display settings were not saved. Check your connection and try again.';
|
||||
let status = httpSuccess ? 'success' : 'error';
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if (data.message) message = data.message;
|
||||
// A body can refine a successful verdict but never overturn a failed one.
|
||||
if (httpSuccess && data.status) status = data.status;
|
||||
} catch {
|
||||
// Non-JSON body — fall back to the status-code verdict above.
|
||||
}
|
||||
showNotification(message, status);
|
||||
};
|
||||
|
||||
// Vegas Scroll Mode Settings
|
||||
(function() {
|
||||
// Escape HTML to prevent XSS
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = String(text || '');
|
||||
return div.innerHTML;
|
||||
}
|
||||
function escapeHtml(text) { return window.LEDEscape.html(text); }
|
||||
|
||||
// Escape for use in HTML attributes
|
||||
function escapeAttr(text) {
|
||||
return escapeHtml(text).replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
function escapeAttr(text) { return window.LEDEscape.attr(text); }
|
||||
|
||||
// Toggle settings visibility
|
||||
const vegasEnabledCheckbox = document.getElementById('vegas_scroll_enabled');
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
hx-ext="json-enc"
|
||||
hx-headers='{"Content-Type": "application/json"}'
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="showNotification(event.detail.xhr.responseJSON?.message || 'Durations saved', event.detail.xhr.responseJSON?.status || 'success')"
|
||||
hx-on:htmx:after-request="showSaveResult(event.detail.xhr, 'Durations saved', 'Durations were not saved. Check your connection and try again.')"
|
||||
class="space-y-6"
|
||||
novalidate
|
||||
onsubmit="fixInvalidNumberInputs(this); return true;">
|
||||
|
||||
@@ -164,21 +164,6 @@ function initializeFontsTab() {
|
||||
// Reset retry counter on successful init
|
||||
initRetryCount = 0;
|
||||
|
||||
// showNotification is implemented by the notification widget
|
||||
// (widgets/notification.js). If it hasn't loaded, queue the message; the
|
||||
// widget shows queued messages as soon as it registers.
|
||||
if (typeof window.showNotification !== 'function') {
|
||||
window.showNotification = function(message, type = 'info') {
|
||||
const registry = window.LEDMatrixWidgets;
|
||||
const widget = registry && typeof registry.get === 'function' ? registry.get('notification') : null;
|
||||
if (widget && typeof widget.show === 'function') {
|
||||
return widget.show(message, typeof type === 'string' ? { type: type } : (type || {}));
|
||||
}
|
||||
(window.__pendingNotifications = window.__pendingNotifications || []).push([message, type]);
|
||||
console.log(`[${String((type && type.type) || type).toUpperCase()}]`, message);
|
||||
};
|
||||
}
|
||||
|
||||
debugLog('Initializing font management...');
|
||||
initializeFontManagement();
|
||||
|
||||
@@ -349,14 +334,7 @@ async function loadFontData() {
|
||||
// Show error states
|
||||
availableContainer.innerHTML = '<div class="text-red-400">Error loading font data. Please refresh the page.</div>';
|
||||
|
||||
// Only show notification if showNotification is available
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification('Error loading font configuration', 'error');
|
||||
} else if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('Error loading font configuration', 'error');
|
||||
} else {
|
||||
console.error('Error loading font configuration:', error.message);
|
||||
}
|
||||
showNotification('Error loading font configuration', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,24 +11,7 @@
|
||||
hx-ext="json-enc"
|
||||
hx-headers='{"Content-Type": "application/json"}'
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="
|
||||
var xhr = event.detail.xhr;
|
||||
var isSuccess = xhr.status >= 200 && xhr.status < 300;
|
||||
var message = '';
|
||||
var status = 'success';
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
message = data.message || '';
|
||||
status = data.status || status;
|
||||
} catch (e) {}
|
||||
if (isSuccess) {
|
||||
message = message || 'Settings saved';
|
||||
} else {
|
||||
message = message || 'Failed to save settings';
|
||||
status = 'error';
|
||||
}
|
||||
showNotification(message, status);
|
||||
"
|
||||
hx-on:htmx:after-request="showSaveResult(event.detail.xhr, 'Settings saved', 'Failed to save settings')"
|
||||
class="space-y-6">
|
||||
{# Marks this post as the whole form, so an unchecked box saves as
|
||||
false. Without it the save endpoint treats absent keys as unchanged
|
||||
|
||||
@@ -126,8 +126,10 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Global variables - use window properties to avoid redeclaration errors with HTMX reloads
|
||||
// Initialize only if not already defined
|
||||
// Scoped: this script runs at global scope after every HTMX swap, and the
|
||||
// Cache partial has helpers with the same names (showError, escapeHtml). State
|
||||
// that must survive a reload of this partial lives on window._logs* instead.
|
||||
(function() {
|
||||
if (typeof window._logsEventSource === 'undefined') {
|
||||
window._logsEventSource = null;
|
||||
}
|
||||
@@ -624,9 +626,7 @@ function toggleRealtime() {
|
||||
|
||||
function refreshLogs() {
|
||||
loadLogs();
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification('Logs refreshed', 'success');
|
||||
}
|
||||
showNotification('Logs refreshed', 'success');
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
@@ -637,16 +637,12 @@ function clearLogs() {
|
||||
}
|
||||
showEmptyState();
|
||||
updateLogStats();
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification('Logs cleared', 'info');
|
||||
}
|
||||
showNotification('Logs cleared', 'info');
|
||||
}
|
||||
|
||||
function downloadLogs() {
|
||||
if (window._filteredLogs.length === 0) {
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification('No logs to download', 'warning');
|
||||
}
|
||||
showNotification('No logs to download', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -662,9 +658,7 @@ function downloadLogs() {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification('Logs downloaded', 'success');
|
||||
}
|
||||
showNotification('Logs downloaded', 'success');
|
||||
}
|
||||
|
||||
// Track if user is near bottom for smart auto-scroll
|
||||
@@ -776,16 +770,7 @@ function showError(message) {
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to escape HTML. Quotes too: the result is interpolated into
|
||||
// quoted attribute values (option value=, title=), and the
|
||||
// textContent/innerHTML round-trip only escapes &, < and >.
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
function escapeHtml(text) { return window.LEDEscape.html(text); }
|
||||
|
||||
function refreshCurrentPluginStatus() {
|
||||
fetch('/api/v3/display/current-status')
|
||||
@@ -920,15 +905,13 @@ function clearPluginErrors() {
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (typeof showNotification !== 'undefined') {
|
||||
showNotification(data.status === 'success' ? 'Plugin errors cleared'
|
||||
: (data.message || 'Could not clear plugin errors'),
|
||||
data.status === 'success' ? 'success' : 'error');
|
||||
}
|
||||
showNotification(data.status === 'success' ? 'Plugin errors cleared'
|
||||
: (data.message || 'Could not clear plugin errors'),
|
||||
data.status === 'success' ? 'success' : 'error');
|
||||
refreshPluginErrors();
|
||||
})
|
||||
.catch(() => {
|
||||
if (typeof showNotification !== 'undefined') showNotification('Could not clear plugin errors', 'error');
|
||||
showNotification('Could not clear plugin errors', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -946,4 +929,5 @@ window.addEventListener('beforeunload', function() {
|
||||
window._pluginErrorsPollTimer = null;
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -260,18 +260,10 @@
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
function escapeHtml(text) { return window.LEDEscape.html(text); }
|
||||
|
||||
function showError(message) {
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification(message, 'error');
|
||||
} else {
|
||||
alert(message);
|
||||
}
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
// Populate plugin filter
|
||||
|
||||
@@ -216,11 +216,7 @@
|
||||
if (nowDone) maybeAutoHide();
|
||||
return;
|
||||
}
|
||||
var appEl = document.querySelector('[x-data="app()"]') || document.querySelector('[x-data]');
|
||||
// Same two-tier resolution as settings-search.js's getAppData():
|
||||
// _x_dataStack on current Alpine, __x.$data as an older-API fallback.
|
||||
var data = appEl && ((appEl._x_dataStack && appEl._x_dataStack[0]) ||
|
||||
(appEl.__x && appEl.__x.$data));
|
||||
var data = window.getApp();
|
||||
if (data) {
|
||||
data.activeTab = btn.dataset.tab;
|
||||
if ('mobileNavOpen' in data) data.mobileNavOpen = false;
|
||||
@@ -443,7 +439,7 @@
|
||||
<button hx-post="/api/v3/system/action"
|
||||
hx-vals='{"action": "start_display"}'
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined') { var m='Display started',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s); }"
|
||||
hx-on:htmx:after-request="var m='Display started',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s);"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-green-600 hover:bg-green-700">
|
||||
<i class="fas fa-play mr-2"></i>
|
||||
Start Display
|
||||
@@ -452,7 +448,7 @@
|
||||
<button hx-post="/api/v3/system/action"
|
||||
hx-vals='{"action": "stop_display"}'
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined') { var m='Display stopped',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s); }"
|
||||
hx-on:htmx:after-request="var m='Display stopped',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s);"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-red-600 hover:bg-red-700">
|
||||
<i class="fas fa-stop mr-2"></i>
|
||||
Stop Display
|
||||
@@ -462,7 +458,7 @@
|
||||
hx-vals='{"action": "git_pull"}'
|
||||
hx-confirm="This will stash any local changes and update the code. Continue?"
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined') { var m='Code update completed',s='info'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s); }"
|
||||
hx-on:htmx:after-request="var m='Code update completed',s='info'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s);"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50">
|
||||
<i class="fas fa-download mr-2"></i>
|
||||
Update Code
|
||||
@@ -472,7 +468,7 @@
|
||||
hx-vals='{"action": "reboot_system"}'
|
||||
hx-confirm="Are you sure you want to reboot the system?"
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined') { var m='System rebooting...',s='info'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s); }"
|
||||
hx-on:htmx:after-request="var m='System rebooting...',s='info'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s);"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-yellow-600 hover:bg-yellow-700">
|
||||
<i class="fas fa-power-off mr-2"></i>
|
||||
Reboot System
|
||||
@@ -482,7 +478,7 @@
|
||||
hx-vals='{"action": "shutdown_system"}'
|
||||
hx-confirm="Are you sure you want to shut down the system? This will power off the Raspberry Pi."
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined') { var m='System shutting down...',s='info'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s); }"
|
||||
hx-on:htmx:after-request="var m='System shutting down...',s='info'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s);"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-red-800 hover:bg-red-900">
|
||||
<i class="fas fa-power-off mr-2"></i>
|
||||
Shutdown System
|
||||
@@ -491,7 +487,7 @@
|
||||
<button hx-post="/api/v3/system/action"
|
||||
hx-vals='{"action": "restart_display_service"}'
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined') { var m='Display service restarted',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s); }"
|
||||
hx-on:htmx:after-request="var m='Display service restarted',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s);"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50">
|
||||
<i class="fas fa-redo mr-2"></i>
|
||||
Restart Display Service
|
||||
@@ -500,7 +496,7 @@
|
||||
<button hx-post="/api/v3/system/action"
|
||||
hx-vals='{"action": "restart_web_service"}'
|
||||
hx-swap="none"
|
||||
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined') { var m='Web service restarted',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s); }"
|
||||
hx-on:htmx:after-request="var m='Web service restarted',s='success'; try { var d=JSON.parse(event.detail.xhr.responseText); m=d.message||m; s=d.status||s; } catch(e) { s=(event.detail.xhr&&event.detail.xhr.status>=400?'error':s); } showNotification(m,s);"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50">
|
||||
<i class="fas fa-redo mr-2"></i>
|
||||
Restart Web Service
|
||||
|
||||
@@ -423,11 +423,10 @@
|
||||
{% if img.get('size') %}{{ (img.get('size') / 1024)|round }} KB{% endif %}
|
||||
{% if img.get('uploaded_at') %} • {{ img.get('uploaded_at') }}{% endif %}
|
||||
</p>
|
||||
{% if has_schedule %}
|
||||
<p class="text-xs text-blue-600 mt-1">
|
||||
<i class="fas fa-clock mr-1"></i>Scheduled
|
||||
{# file-upload.js rewrites .image-schedule-summary in place when the schedule is edited #}
|
||||
<p class="text-xs text-blue-600 mt-1 image-schedule-summary">
|
||||
<i class="fas fa-clock mr-1"></i>{{ 'Scheduled' if has_schedule else 'Always shown' }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
|
||||
@@ -540,18 +540,6 @@
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
/* CodeMirror editor styles */
|
||||
.CodeMirror {
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 14px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.CodeMirror.cm-error {
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
/* Plugin config view styles */
|
||||
.plugin-config-view {
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
fieldId: 'schedule'
|
||||
});
|
||||
|
||||
console.log('[Schedule] Schedule picker widget initialized');
|
||||
debugLog('[Schedule] Schedule picker widget initialized');
|
||||
}
|
||||
|
||||
// Handle form submission response
|
||||
@@ -165,28 +165,7 @@
|
||||
const type = response.status || 'info';
|
||||
|
||||
// Use global notification function if available
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification(message, type);
|
||||
} else {
|
||||
// Fallback notification
|
||||
const colors = {
|
||||
success: 'bg-green-500',
|
||||
error: 'bg-red-500',
|
||||
warning: 'bg-yellow-500',
|
||||
info: 'bg-blue-500'
|
||||
};
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 ' + (colors[type] || colors.info) + ' text-white';
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(function() {
|
||||
notification.style.transition = 'opacity 0.5s';
|
||||
notification.style.opacity = '0';
|
||||
setTimeout(function() { notification.remove(); }, 500);
|
||||
}, 3000);
|
||||
}
|
||||
window.showNotification(message, type);
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready or if already loaded
|
||||
@@ -265,7 +244,7 @@
|
||||
fieldId: 'dim_schedule'
|
||||
});
|
||||
|
||||
console.log('[DimSchedule] Dim schedule picker widget initialized');
|
||||
debugLog('[DimSchedule] Dim schedule picker widget initialized');
|
||||
}
|
||||
|
||||
// Handle dim schedule form submission response
|
||||
@@ -282,28 +261,7 @@
|
||||
const type = response.status || 'info';
|
||||
|
||||
// Use global notification function if available
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification(message, type);
|
||||
} else {
|
||||
// Fallback notification
|
||||
const colors = {
|
||||
success: 'bg-green-500',
|
||||
error: 'bg-red-500',
|
||||
warning: 'bg-yellow-500',
|
||||
info: 'bg-blue-500'
|
||||
};
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 ' + (colors[type] || colors.info) + ' text-white';
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(function() {
|
||||
notification.style.transition = 'opacity 0.5s';
|
||||
notification.style.opacity = '0';
|
||||
setTimeout(function() { notification.remove(); }, 500);
|
||||
}, 3000);
|
||||
}
|
||||
window.showNotification(message, type);
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready
|
||||
|
||||
@@ -344,21 +344,15 @@ function forceRenderStarlarkApp(appId) {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.status === 'success') {
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification('Rendered successfully! ' + (data.frame_count || 0) + ' frame(s)', 'success');
|
||||
} else {
|
||||
alert('Rendered successfully! ' + (data.frame_count || 0) + ' frame(s)');
|
||||
}
|
||||
showNotification('Rendered successfully! ' + (data.frame_count || 0) + ' frame(s)', 'success');
|
||||
} else {
|
||||
var msg = 'Render failed: ' + (data.message || 'Unknown error');
|
||||
if (typeof showNotification === 'function') showNotification(msg, 'error');
|
||||
else alert(msg);
|
||||
showNotification(msg, 'error');
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
var msg = 'Render failed: ' + err.message;
|
||||
if (typeof showNotification === 'function') showNotification(msg, 'error');
|
||||
else alert(msg);
|
||||
showNotification(msg, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -371,22 +365,20 @@ function toggleStarlarkApp(appId, enabled) {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.status === 'success') {
|
||||
if (typeof loadInstalledPlugins === 'function') loadInstalledPlugins();
|
||||
else if (typeof window.loadInstalledPlugins === 'function') window.loadInstalledPlugins();
|
||||
// A failed reload has already been reported by loadInstalledPlugins.
|
||||
window.pluginManager.loadInstalledPlugins(true).catch(function() {});
|
||||
var container = document.getElementById('plugin-config-starlark:' + appId);
|
||||
if (container && window.htmx) {
|
||||
htmx.ajax('GET', '/v3/partials/plugin-config/starlark:' + encodeURIComponent(appId), {target: container, swap: 'innerHTML'});
|
||||
}
|
||||
} else {
|
||||
var msg = 'Toggle failed: ' + (data.message || 'Unknown error');
|
||||
if (typeof showNotification === 'function') showNotification(msg, 'error');
|
||||
else alert(msg);
|
||||
showNotification(msg, 'error');
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
var msg = 'Toggle failed: ' + err.message;
|
||||
if (typeof showNotification === 'function') showNotification(msg, 'error');
|
||||
else alert(msg);
|
||||
showNotification(msg, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -436,8 +428,7 @@ function saveStarlarkConfig(appId) {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.status === 'success') {
|
||||
if (typeof showNotification === 'function') showNotification('Configuration saved!', 'success');
|
||||
else alert('Configuration saved!');
|
||||
showNotification('Configuration saved!', 'success');
|
||||
// Reload partial to reflect updated status
|
||||
var container = document.getElementById('plugin-config-starlark:' + appId);
|
||||
if (container && window.htmx) {
|
||||
@@ -445,14 +436,12 @@ function saveStarlarkConfig(appId) {
|
||||
}
|
||||
} else {
|
||||
var msg = 'Save failed: ' + (data.message || 'Unknown error');
|
||||
if (typeof showNotification === 'function') showNotification(msg, 'error');
|
||||
else alert(msg);
|
||||
showNotification(msg, 'error');
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
var msg = 'Save failed: ' + err.message;
|
||||
if (typeof showNotification === 'function') showNotification(msg, 'error');
|
||||
else alert(msg);
|
||||
showNotification(msg, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -435,9 +435,7 @@
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
function escHtml(s) { return window.LEDEscape.html(s); }
|
||||
|
||||
// ── main action dispatcher ────────────────────────────────────────────────
|
||||
|
||||
@@ -910,11 +908,7 @@
|
||||
};
|
||||
|
||||
// ── plugin health panel ──────────────────────────────────────────────────
|
||||
function phEscape(s) {
|
||||
return String(s).replace(/[&<>"']/g, function (c) {
|
||||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
||||
});
|
||||
}
|
||||
function phEscape(s) { return window.LEDEscape.html(s); }
|
||||
function phFmtSecs(v) {
|
||||
if (typeof v !== 'number' || !isFinite(v)) return '—';
|
||||
return v.toFixed(3) + 's';
|
||||
|
||||
Reference in New Issue
Block a user