Files
LEDMatrix/web_interface/templates/v3
Chuck 7603909c59 feat(ui): add reusable json-file-manager widget (#352)
* feat(ui): add reusable json-file-manager widget for plugin file management

Introduces JsonFileManager — a zero-CDN, keyboard-accessible, configurable
widget for managing JSON data files from plugin configuration forms.

web_interface/static/v3/js/widgets/json-file-manager.js (new):
- Self-contained class with scoped CSS (no global leakage)
- File list with cards: enable/disable toggle, entry count, size, date
- Drag-and-drop + click-to-browse JSON upload
- Textarea-based JSON editor (no CDN); Format + Validate buttons
- Ctrl+S to save, Escape to close any open modal
- Create-new-file modal with configurable fields and validation
- Delete confirmation modal
- All actions (list/get/save/upload/delete/create/toggle) are configurable
  via x-widget-config in config_schema.json — no plugin-ID hardcoding

web_interface/static/v3/plugins_manager.js:
- New handler for x-widget: "json-file-manager" — renders mount div,
  instantiates JsonFileManager with x-widget-config and plugin ID

web_interface/templates/v3/base.html:
- Include json-file-manager.js (defer) before plugins_manager.js

Usage: set x-widget: "json-file-manager" + x-widget-config in any
plugin's config_schema.json (see ledmatrix-plugins of-the-day for a
complete example).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(json-file-manager): review fixes — type=button, finally, display_name, instance tracking

- Add type="button" to every button in the template (replace_all) so none
  default to submit inside the plugin-config-form
- Wrap _doSave/_doDelete/_doCreate fetch blocks in try/finally so _idle()
  always fires, not only on the error path
- _doCreate validation: skip the required-check for display_name (f.key
  !== 'display_name') and only validate pattern when val is non-empty, so
  the auto-derive logic at the end of the loop can run; simplify the
  derive block to a single conditional instead of nested DOM lookups
- plugins_manager.js: track instances in window.__jfmInstances[safeFieldId]
  and call _destroy() on any previous instance before mounting a new one,
  preventing duplicate keydown handlers when the config form is re-rendered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(json-file-manager): use validity.patternMismatch; destroy all instances on remount

- Replace `new RegExp(f.pattern).test(val)` with `el.validity.patternMismatch`
  to avoid potential SyntaxError from untrusted pattern strings and rely on the
  browser's already-validated pattern attribute instead
- plugins_manager.js: iterate all window.__jfmInstances and call _destroy() on
  every entry before mounting, then reset the map, so no orphaned keydown
  handlers survive when any plugin config form is re-rendered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins_manager): scope jfm instance teardown to current mount key only

The global sweep (Object.values + window.__jfmInstances = {}) destroyed
sibling file-manager widgets when any one of them was remounted. Replace
with a targeted destroy of window.__jfmInstances[safeFieldId] only,
leaving all other entries untouched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(json-file-manager): address Codacy security warnings

- Replace Math.random() with crypto.getRandomValues() for UID generation
- Remove unused variable `u` in _card()
- Guard this.actions property access with hasOwnProperty
- Replace btn.innerHTML in _busy/_idle with DOM manipulation + textContent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:46:38 -04:00
..