mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-11 01:26:37 +00:00
Merge origin/main into claude/remove-skins-and-base-classes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -985,6 +985,10 @@ def save_main_config():
|
||||
mode_key = field[len('duration__'):]
|
||||
if not mode_key:
|
||||
continue
|
||||
if raw_value is None or str(raw_value).strip() == '':
|
||||
# Blank means "use the plugin's own duration".
|
||||
current_config['display']['display_durations'].pop(mode_key, None)
|
||||
continue
|
||||
try:
|
||||
int_value = int(raw_value)
|
||||
except (ValueError, TypeError, OverflowError):
|
||||
|
||||
@@ -409,6 +409,8 @@ def manage_plugin_limits(plugin_id):
|
||||
@api_v3.route('/plugins/toggle', methods=['POST'])
|
||||
def toggle_plugin():
|
||||
"""Toggle plugin enabled/disabled"""
|
||||
plugin_id = None
|
||||
enabled = None
|
||||
try:
|
||||
if not api_v3.plugin_manager or not api_v3.config_manager:
|
||||
return jsonify({'status': 'error', 'message': 'Plugin or config manager not initialized'}), 500
|
||||
@@ -506,21 +508,21 @@ def toggle_plugin():
|
||||
message=f"Plugin {plugin_id} {'enabled' if enabled else 'disabled'} successfully"
|
||||
)
|
||||
except Exception as e:
|
||||
from src.web_interface.errors import WebInterfaceError
|
||||
error = WebInterfaceError.from_exception(e, ErrorCode.PLUGIN_OPERATION_CONFLICT)
|
||||
# Not PLUGIN_OPERATION_CONFLICT: that told the user "an operation is
|
||||
# already in progress" whatever actually went wrong.
|
||||
logger.error('Error toggling plugin %s', plugin_id, exc_info=True)
|
||||
if api_v3.operation_history:
|
||||
toggle_type = "enable" if ('data' in locals() and data.get('enabled')) else "disable"
|
||||
api_v3.operation_history.record_operation(
|
||||
toggle_type,
|
||||
plugin_id=data.get('plugin_id') if 'data' in locals() else None,
|
||||
"enable" if enabled else "disable",
|
||||
plugin_id=plugin_id,
|
||||
status="failed",
|
||||
error=str(e)
|
||||
)
|
||||
action = 'enable' if enabled else 'disable' if enabled is not None else 'toggle'
|
||||
return error_response(
|
||||
error.error_code,
|
||||
error.message,
|
||||
details=error.details,
|
||||
context=error.context,
|
||||
ErrorCode.UNKNOWN_ERROR,
|
||||
f"Failed to {action} plugin {plugin_id or ''}".rstrip(),
|
||||
details=describe_exception(e),
|
||||
status_code=500
|
||||
)
|
||||
@api_v3.route('/plugins/operation/<operation_id>', methods=['GET'])
|
||||
|
||||
@@ -552,14 +552,39 @@ def _load_display_partial():
|
||||
logger.error("Error loading partial", exc_info=True)
|
||||
return "Error loading partial", 500
|
||||
|
||||
def _plugin_default_duration(plugin_id, plugin_config):
|
||||
"""Seconds a plugin shows each screen when the Rotation page sets none.
|
||||
|
||||
Mirrors BasePlugin.get_display_duration's config fallback: the plugin's
|
||||
display_duration, else its schema default, else 15.
|
||||
"""
|
||||
def _valid(v):
|
||||
return isinstance(v, (int, float)) and not isinstance(v, bool) and v > 0
|
||||
|
||||
value = plugin_config.get('display_duration')
|
||||
if not _valid(value):
|
||||
schema = None
|
||||
schema_mgr = getattr(pages_v3, 'schema_manager', None)
|
||||
if schema_mgr is not None:
|
||||
try:
|
||||
schema = schema_mgr.load_schema(plugin_id)
|
||||
except Exception:
|
||||
logger.debug("durations: no schema for %s", plugin_id, exc_info=True)
|
||||
value = plugin_config_defaults(schema if isinstance(schema, dict) else None).get(
|
||||
'display_duration')
|
||||
if not _valid(value):
|
||||
value = 15
|
||||
return int(value) if float(value).is_integer() else value
|
||||
|
||||
|
||||
def _load_durations_partial():
|
||||
"""Load rotation & durations partial.
|
||||
|
||||
Builds one duration entry per display mode of every enabled plugin
|
||||
(falling back to the display controller's 30s default), overlaid with any
|
||||
values saved in display.display_durations. Historically the template only
|
||||
looped over saved keys, and nothing ever populated them, so the page
|
||||
rendered empty.
|
||||
Builds one duration entry per display mode of every enabled plugin. A mode
|
||||
with a value saved in display.display_durations shows it; the rest are
|
||||
blank, with the plugin's own duration as the placeholder, because a saved
|
||||
value overrides the plugin (see DisplayController._get_display_duration).
|
||||
Pre-filling every mode would pin them all on the first save.
|
||||
"""
|
||||
try:
|
||||
if pages_v3.config_manager:
|
||||
@@ -578,10 +603,12 @@ def _load_durations_partial():
|
||||
continue
|
||||
modes = pages_v3.plugin_manager.get_plugin_display_modes(pid) or [pid]
|
||||
covered_keys.update(modes)
|
||||
default = _plugin_default_duration(pid, main_config.get(pid, {}) or {})
|
||||
duration_groups.append({
|
||||
'plugin_id': pid,
|
||||
'plugin_name': info.get('name') or pid,
|
||||
'modes': [{'key': m, 'value': saved.get(m, 30)} for m in modes],
|
||||
'modes': [{'key': m, 'value': saved.get(m, ''), 'default': default}
|
||||
for m in modes],
|
||||
})
|
||||
# Saved keys not owned by any enabled plugin (disabled or
|
||||
# uninstalled plugins) stay visible rather than vanishing.
|
||||
|
||||
Reference in New Issue
Block a user