mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 22:35:08 +00:00
feat(web): show which plugins use each font; warn before deleting one (#619)
* feat(web): show which plugins use each font, warn before deleting one
The Fonts tab lists font files from the web process's own scan, and the
plugins that register fonts run in the display process, so the tab had no
way to say whether a font was in use before deleting it.
The display service now publishes {catalog key: [plugin ids]} to the
shared cache (font_usage_snapshot, src/font_usage.py), built from the
loaded plugins' FontManager.register_manager_font() registrations. A
daemon thread checks every 10 s and writes only when the usage changed
(plus a daily refresh so cache cleanup cannot expire it); it never raises.
Families, aliases (press_start, four_by_six, ...) and paths are resolved
through FontManager's catalog to the file stem the Fonts tab keys rows by;
fonts outside assets/fonts are left out. Unloading a plugin drops its
registrations (new FontManager.forget_manager_fonts).
GET /api/v3/fonts/catalog merges used_by into each row per request (the
5-minute scan cache is copied, never edited): a list of plugin ids, or
null when the display service has not reported. The tab shows a Used by
column ("unknown" / "-" / ids, rendered as text) and deleting an in-use
font names the plugins in the confirmation, from a fresh read. The server
still refuses only system fonts. Catalog fetches bypass the browser's
5-second API cache, which otherwise served the pre-delete list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: call forget_manager_fonts through a hasattr check pylint can follow
getattr(..., None) then callable() is fine at runtime, but pylint's E1102
("not callable") can't see through it, and Codacy fails the check on it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+30
-2
@@ -173,8 +173,10 @@ font = self.font_manager.resolve_font(
|
||||
> install directory.
|
||||
>
|
||||
> The web UI's **Fonts** tab lists, uploads, previews and deletes the
|
||||
> font files in `assets/fonts/`. It does not show fonts registered
|
||||
> through `register_manager_font()` and has no override editor (the
|
||||
> font files in `assets/fonts/`. Its **Used by** column shows which
|
||||
> loaded plugins registered each file through `register_manager_font()`
|
||||
> (see [Font usage in the web UI](#font-usage-in-the-web-ui)), and it
|
||||
> warns before deleting one of them. It has no override editor (the
|
||||
> override panels and `/api/v3/fonts/overrides` endpoints were removed).
|
||||
> The programmatic override workflow in
|
||||
> [Manual Font Overrides](#manual-font-overrides) below still works.
|
||||
@@ -276,6 +278,31 @@ Place font files in `assets/fonts/` directory:
|
||||
- Font family name is derived from filename (without extension)
|
||||
- Will be automatically discovered on next initialization
|
||||
|
||||
## Font usage in the web UI
|
||||
|
||||
The web interface runs in its own process and has no FontManager, so the
|
||||
display service publishes which plugin uses which font
|
||||
(`src/font_usage.py`), and the Fonts tab's **Used by** column reads it:
|
||||
|
||||
- **Source**: `register_manager_font()` registrations of the loaded
|
||||
plugins. `get_font()` and `resolve_font()` do not know the calling plugin
|
||||
and are not counted, and neither is a plugin that opens a font file
|
||||
directly with PIL — register the fonts your plugin draws with if you want
|
||||
them listed.
|
||||
- **Names**: a family, alias (`press_start`, `four_by_six`,
|
||||
`five_by_seven`, `tom_thumb`) or path is resolved through
|
||||
`font_catalog` to the file it loads and reported under that file's name
|
||||
without extension (`PressStart2P-Regular`, `4x6-font`, `5x7`,
|
||||
`tom-thumb`), which is how the Fonts tab keys its rows. Fonts outside
|
||||
`assets/fonts/` (a plugin's own `plugin_id::family` fonts) and families
|
||||
that resolve to nothing are left out.
|
||||
- **When**: a daemon thread started once plugins have loaded checks every
|
||||
10 seconds and writes the `font_usage_snapshot` cache key only when the
|
||||
usage changed (and once a day, so the cache's cleanup never expires it).
|
||||
Unloading a plugin drops its registrations (`forget_manager_fonts`).
|
||||
- **Unknown**: until the display service has published, the column reads
|
||||
"unknown" and `GET /api/v3/fonts/catalog` returns `used_by: null`.
|
||||
|
||||
## Performance Monitoring
|
||||
|
||||
```python
|
||||
@@ -374,6 +401,7 @@ self.font = self.font_manager.resolve_font(
|
||||
### FontManager Methods
|
||||
|
||||
- `register_manager_font(manager_id, element_key, family, size_px, color=None)` - Register font usage
|
||||
- `forget_manager_fonts(manager_id)` - Drop a manager's registrations (core calls it when a plugin unloads)
|
||||
- `resolve_font(element_key, family, size_px, plugin_id=None)` - Get font with override support
|
||||
- `get_font(family, size_px)` - Get font directly (bypasses overrides)
|
||||
- `measure_text(text, font)` - Measure text dimensions
|
||||
|
||||
@@ -1489,23 +1489,37 @@ Delete a stored backup.
|
||||
|
||||
Fonts in `assets/fonts/`, keyed by file name without extension.
|
||||
|
||||
`used_by` lists the plugins that use the font, as the display service last
|
||||
reported: the loaded plugins that registered it through
|
||||
`FontManager.register_manager_font()`, under whatever family, alias
|
||||
(`press_start`, `four_by_six`, ...) or path they gave. It is `[]` when no
|
||||
loaded plugin registered it and `null` when the display service has not
|
||||
reported yet (`font_usage.available` is then `false`). A plugin that opens a
|
||||
font file directly is not counted, so `[]` does not prove a font is unused.
|
||||
The file scan is cached for 5 minutes; `used_by` is read on every request.
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"catalog": {
|
||||
"press_start": {
|
||||
"filename": "press_start.ttf",
|
||||
"PressStart2P-Regular": {
|
||||
"filename": "PressStart2P-Regular.ttf",
|
||||
"family_name": "Press Start 2P",
|
||||
"display_name": "Press Start 2P",
|
||||
"path": "assets/fonts/press_start.ttf",
|
||||
"path": "assets/fonts/PressStart2P-Regular.ttf",
|
||||
"type": "ttf",
|
||||
"is_system": true,
|
||||
"scalable": true,
|
||||
"native_size": null,
|
||||
"metadata": { ... }
|
||||
"metadata": { ... },
|
||||
"used_by": ["calendar", "hello-world"]
|
||||
}
|
||||
},
|
||||
"font_usage": {
|
||||
"available": true,
|
||||
"generated_at": "2026-09-23T10:00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1560,7 +1574,9 @@ Upload a custom font file. It is saved as `assets/fonts/<font_family><ext>`.
|
||||
**DELETE** `/api/v3/fonts/<font_family>`
|
||||
|
||||
Delete an uploaded font (`<font_family>` is the file name without
|
||||
extension). System fonts answer `403`.
|
||||
extension). System fonts answer `403`. A font a plugin uses is not refused;
|
||||
the Fonts tab names those plugins (the catalog's `used_by`) in its
|
||||
confirmation first.
|
||||
|
||||
### Font Preview
|
||||
|
||||
|
||||
Reference in New Issue
Block a user