mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
These docs describe features that exist as documented in the doc but either never wired up or regressed when v3 shipped. Each gets a clear status banner so plugin authors don't waste time chasing features that don't actually work. FONT_MANAGER.md - The "For Plugin Developers / Plugin Font Registration" section documents adding a "fonts" block to manifest.json that gets registered via FontManager.register_plugin_fonts(). The method exists at src/font_manager.py:150 but is **never called from anywhere** in the codebase (verified: zero callers). A plugin shipping a manifest "fonts" block has its fonts silently ignored. Added a status warning and a note about how to actually ship plugin fonts (regular files in the plugin dir, loaded directly). PLUGIN_IMPLEMENTATION_SUMMARY.md - Added a top-level status banner. - Architecture diagram referenced src/plugin_system/registry_manager.py (which doesn't exist) and listed plugins/ as the install location. Replaced with the real file list (plugin_loader, schema_manager, health_monitor, operation_queue, state_manager) and pointed at plugin-repos/ as the default install location. - "Dependency Management: Virtual Environments" — verified there's no per-plugin venv. Removed the bullet and added a note that plugin Python deps install into the system Python environment, with no conflict resolution. - "Permission System: File Access Control / Network Access / Resource Limits / CPU and memory constraints" — none of these exist. There's a resource_monitor.py and health_monitor.py for metrics/warnings, but no hard caps or sandboxing. Replaced the section with what's actually implemented and a clear note that plugins run in the same process with full file/network access. PLUGIN_CUSTOM_ICONS.md and PLUGIN_CUSTOM_ICONS_FEATURE.md - The custom-icon feature was implemented in the v2 web interface via a getPluginIcon() helper in templates/index_v2.html that read the manifest "icon" field. When the v3 web interface was built, that helper wasn't ported. Verified in web_interface/templates/v3/base.html:515 and :774, plugin tab icons are hardcoded to `fas fa-puzzle-piece`. The "icon" field in plugin manifests is currently silently ignored (verified with grep across web_interface/ and src/plugin_system/ — zero non-action- related reads of plugin.icon or manifest.icon). - Added a status banner to both docs noting the regression so plugin authors don't think their custom icons are broken in their own plugin code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
436 lines
11 KiB
Markdown
436 lines
11 KiB
Markdown
# Plugin Custom Icons Feature
|
|
|
|
> ⚠️ **Status:** this doc describes the v2 web interface
|
|
> implementation of plugin custom icons. The feature **regressed when
|
|
> the v3 web interface was built** — the `getPluginIcon()` helper
|
|
> referenced below lived in `templates/index_v2.html` (which is now
|
|
> archived) and was not ported to the v3 templates. Plugin tab icons
|
|
> in v3 are hardcoded to `fas fa-puzzle-piece`
|
|
> (`web_interface/templates/v3/base.html:515` and `:774`). The
|
|
> `icon` field in `manifest.json` is currently silently ignored.
|
|
|
|
## What Was Implemented
|
|
|
|
You asked: **"How could a plugin add their own custom icon?"**
|
|
|
|
**Answer:** Plugins can now specify custom icons in their `manifest.json` file using the `icon` field!
|
|
|
|
## Features Delivered
|
|
|
|
✅ **Font Awesome Support** - Use any Font Awesome icon (e.g., `fas fa-clock`)
|
|
✅ **Emoji Support** - Use any emoji character (e.g., `⏰` or `👋`)
|
|
✅ **Custom Image Support** - Use custom image files or URLs
|
|
✅ **Automatic Detection** - System automatically detects icon type
|
|
✅ **Fallback Support** - Default puzzle piece icon if none specified
|
|
✅ **Tab & Header Icons** - Icons appear in both tab buttons and configuration page headers
|
|
|
|
## How It Works
|
|
|
|
### For Plugin Developers
|
|
|
|
Simply add an `icon` field to your plugin's `manifest.json`:
|
|
|
|
```json
|
|
{
|
|
"id": "my-plugin",
|
|
"name": "My Plugin",
|
|
"icon": "fas fa-star", // ← Add this line
|
|
"config_schema": "config_schema.json",
|
|
...
|
|
}
|
|
```
|
|
|
|
### Three Icon Types Supported
|
|
|
|
#### 1. Font Awesome Icons (Recommended)
|
|
```json
|
|
"icon": "fas fa-clock"
|
|
```
|
|
|
|
Best for: Professional, consistent UI appearance
|
|
|
|
#### 2. Emoji Icons (Fun!)
|
|
```json
|
|
"icon": "⏰"
|
|
```
|
|
|
|
Best for: Colorful, fun plugins; no setup needed
|
|
|
|
#### 3. Custom Images
|
|
```json
|
|
"icon": "/plugins/my-plugin/logo.png"
|
|
```
|
|
|
|
Best for: Unique branding; requires image file
|
|
|
|
## Implementation Details
|
|
|
|
### Frontend Changes (`templates/index_v2.html`)
|
|
|
|
**New Function: `getPluginIcon(plugin)`**
|
|
- Checks if plugin has `icon` field in manifest
|
|
- Detects icon type automatically:
|
|
- Contains `fa-` → Font Awesome
|
|
- 1-4 characters → Emoji
|
|
- Starts with URL/path → Custom image
|
|
- Otherwise → Default puzzle piece
|
|
|
|
**Updated Functions:**
|
|
- `generatePluginTabs()` - Uses custom icon for tab button
|
|
- `generatePluginConfigForm()` - Uses custom icon in page header
|
|
|
|
### Example Plugin Updates
|
|
|
|
**hello-world plugin:**
|
|
```json
|
|
"icon": "👋"
|
|
```
|
|
|
|
**clock-simple plugin:**
|
|
```json
|
|
"icon": "fas fa-clock"
|
|
```
|
|
|
|
## Code Example
|
|
|
|
Here's what the icon detection logic does. **Important:** Plugin manifests must be treated as untrusted input and require escaping/validation before rendering.
|
|
|
|
```javascript
|
|
// Helper function to escape HTML entities
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Helper function to validate and sanitize image URLs
|
|
function isValidImageUrl(url) {
|
|
if (!url || typeof url !== 'string') {
|
|
return false;
|
|
}
|
|
|
|
// Only allow http, https, or relative paths starting with /
|
|
const allowedProtocols = ['http:', 'https:'];
|
|
const urlLower = url.toLowerCase().trim();
|
|
|
|
// Reject dangerous protocols
|
|
if (urlLower.startsWith('javascript:') ||
|
|
urlLower.startsWith('data:') ||
|
|
urlLower.startsWith('vbscript:') ||
|
|
urlLower.startsWith('onerror=') ||
|
|
urlLower.startsWith('onload=')) {
|
|
return false;
|
|
}
|
|
|
|
// Allow relative paths starting with /
|
|
if (url.startsWith('/')) {
|
|
return true;
|
|
}
|
|
|
|
// Validate absolute URLs
|
|
try {
|
|
const urlObj = new URL(url);
|
|
return allowedProtocols.includes(urlObj.protocol);
|
|
} catch (e) {
|
|
// Invalid URL format
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Helper function to safely validate Font Awesome class names
|
|
function isValidFontAwesomeClass(icon) {
|
|
// Whitelist pattern: only allow alphanumeric, dash, underscore, and spaces
|
|
// Must contain 'fa-' for Font Awesome
|
|
const faPattern = /^[a-zA-Z0-9\s_-]*fa-[a-zA-Z0-9-]+[a-zA-Z0-9\s_-]*$/;
|
|
return faPattern.test(icon) && icon.includes('fa-');
|
|
}
|
|
|
|
function getPluginIcon(plugin) {
|
|
if (plugin.icon) {
|
|
const icon = String(plugin.icon).trim();
|
|
|
|
// Font Awesome icon - escape class name to prevent XSS
|
|
if (isValidFontAwesomeClass(icon)) {
|
|
const escapedIcon = escapeHtml(icon);
|
|
return `<i class="${escapedIcon}"></i>`;
|
|
}
|
|
|
|
// Emoji - use textContent to safely render (no HTML injection possible)
|
|
if (icon.length <= 4) {
|
|
// Create element and set textContent (safe from XSS)
|
|
const span = document.createElement('span');
|
|
span.style.fontSize = '1.1em';
|
|
span.textContent = icon; // textContent automatically escapes
|
|
return span.outerHTML;
|
|
}
|
|
|
|
// Custom image - validate URL and set src attribute safely
|
|
if (isValidImageUrl(icon)) {
|
|
// Create img element and set attributes safely
|
|
const img = document.createElement('img');
|
|
img.src = icon; // URL already validated
|
|
img.alt = '';
|
|
img.style.width = '16px';
|
|
img.style.height = '16px';
|
|
return img.outerHTML;
|
|
}
|
|
}
|
|
|
|
// Default fallback
|
|
return '<i class="fas fa-puzzle-piece"></i>';
|
|
}
|
|
```
|
|
|
|
**Security Notes:**
|
|
- Plugin manifests are treated as untrusted input
|
|
- All text content is escaped using `escapeHtml()` or `textContent`
|
|
- Image URLs are validated to only allow `http://`, `https://`, or relative paths starting with `/`
|
|
- Dangerous protocols (`javascript:`, `data:`, etc.) are explicitly rejected
|
|
- Font Awesome class names are validated against a whitelist pattern
|
|
- DOM elements are created and attributes set directly rather than using string interpolation
|
|
|
|
## Visual Examples
|
|
|
|
### Before (No Custom Icons)
|
|
```
|
|
[🧩 Hello World] [🧩 Clock Simple] [🧩 Weather Display]
|
|
```
|
|
|
|
### After (With Custom Icons)
|
|
```
|
|
[👋 Hello World] [⏰ Clock Simple] [☀️ Weather Display]
|
|
```
|
|
|
|
## Documentation Created
|
|
|
|
📚 **Comprehensive guide:** `docs/PLUGIN_CUSTOM_ICONS.md`
|
|
|
|
Contains:
|
|
- Complete icon type explanations
|
|
- Font Awesome icon recommendations by category
|
|
- Emoji suggestions for common plugin types
|
|
- Custom image guidelines
|
|
- Best practices and troubleshooting
|
|
- Examples for every use case
|
|
|
|
📝 **Updated existing docs:**
|
|
- `PLUGIN_CONFIGURATION_TABS.md` - Added icon reference
|
|
- `PLUGIN_CONFIG_TABS_SUMMARY.md` - Added icon quick tip
|
|
- `PLUGIN_CONFIG_QUICK_START.md` - Added icon bonus section
|
|
|
|
## Popular Icon Recommendations
|
|
|
|
### By Plugin Category
|
|
|
|
**Time & Calendar**
|
|
- Font Awesome: `fas fa-clock`, `fas fa-calendar`, `fas fa-hourglass`
|
|
- Emoji: ⏰ 📅 ⏱️
|
|
|
|
**Weather**
|
|
- Font Awesome: `fas fa-cloud-sun`, `fas fa-temperature-high`
|
|
- Emoji: ☀️ 🌧️ ⛈️
|
|
|
|
**Finance**
|
|
- Font Awesome: `fas fa-chart-line`, `fas fa-dollar-sign`
|
|
- Emoji: 💰 📈 💵
|
|
|
|
**Sports**
|
|
- Font Awesome: `fas fa-football-ball`, `fas fa-trophy`
|
|
- Emoji: ⚽ 🏀 🎮
|
|
|
|
**Music**
|
|
- Font Awesome: `fas fa-music`, `fas fa-headphones`
|
|
- Emoji: 🎵 🎶 🎸
|
|
|
|
**News**
|
|
- Font Awesome: `fas fa-newspaper`, `fas fa-rss`
|
|
- Emoji: 📰 📡 📻
|
|
|
|
**Utilities**
|
|
- Font Awesome: `fas fa-tools`, `fas fa-cog`
|
|
- Emoji: 🔧 ⚙️ 🛠️
|
|
|
|
## Usage Examples
|
|
|
|
### Weather Plugin
|
|
```json
|
|
{
|
|
"id": "weather-pro",
|
|
"name": "Weather Pro",
|
|
"icon": "fas fa-cloud-sun",
|
|
"description": "Advanced weather display"
|
|
}
|
|
```
|
|
Result: `☁️ Weather Pro` tab
|
|
|
|
### Game Scores
|
|
```json
|
|
{
|
|
"id": "game-scores",
|
|
"name": "Game Scores",
|
|
"icon": "🎮",
|
|
"description": "Live game scores"
|
|
}
|
|
```
|
|
Result: `🎮 Game Scores` tab
|
|
|
|
### Custom Branding
|
|
```json
|
|
{
|
|
"id": "company-metrics",
|
|
"name": "Company Metrics",
|
|
"icon": "/plugins/company-metrics/logo.svg",
|
|
"description": "Internal dashboard"
|
|
}
|
|
```
|
|
Result: `[logo] Company Metrics` tab
|
|
|
|
## Benefits
|
|
|
|
### For Users
|
|
- **Visual Recognition** - Instantly identify plugins
|
|
- **Better Navigation** - Find plugins faster
|
|
- **Professional Appearance** - Polished, modern UI
|
|
|
|
### For Developers
|
|
- **Easy to Add** - Just one line in manifest
|
|
- **Flexible Options** - Choose what fits your plugin
|
|
- **No Code Required** - Pure configuration
|
|
|
|
### For the Project
|
|
- **Plugin Differentiation** - Each plugin stands out
|
|
- **Enhanced UX** - More intuitive interface
|
|
- **Branding Support** - Plugins can show identity
|
|
|
|
## Backward Compatibility
|
|
|
|
✅ **Fully backward compatible**
|
|
- Plugins without `icon` field still work
|
|
- Default puzzle piece icon used automatically
|
|
- No breaking changes to existing plugins
|
|
|
|
## Testing
|
|
|
|
To test custom icons:
|
|
|
|
1. **Open web interface** at `http://your-pi-ip:5000`
|
|
2. **Check installed plugins**:
|
|
- Hello World should show 👋
|
|
- Clock Simple should show 🕐
|
|
3. **Install a new plugin** with custom icon
|
|
4. **Verify icon appears** in:
|
|
- Tab navigation bar
|
|
- Plugin configuration page header
|
|
|
|
## File Changes
|
|
|
|
### Modified Files
|
|
- `templates/index_v2.html`
|
|
- Added `getPluginIcon()` function
|
|
- Updated `generatePluginTabs()`
|
|
- Updated `generatePluginConfigForm()`
|
|
|
|
### Updated Plugin Manifests
|
|
- `ledmatrix-plugins/plugins/hello-world/manifest.json` - Added emoji icon
|
|
- `ledmatrix-plugins/plugins/clock-simple/manifest.json` - Added Font Awesome icon
|
|
|
|
### New Documentation
|
|
- `docs/PLUGIN_CUSTOM_ICONS.md` - Complete guide (80+ lines)
|
|
|
|
### Updated Documentation
|
|
- `docs/PLUGIN_CONFIGURATION_TABS.md`
|
|
- `docs/PLUGIN_CONFIG_TABS_SUMMARY.md`
|
|
- `docs/PLUGIN_CONFIG_QUICK_START.md`
|
|
|
|
## Quick Reference
|
|
|
|
### Add Icon to Your Plugin
|
|
|
|
```json
|
|
{
|
|
"id": "your-plugin",
|
|
"name": "Your Plugin Name",
|
|
"icon": "fas fa-star", // or emoji or image URL
|
|
"config_schema": "config_schema.json",
|
|
...
|
|
}
|
|
```
|
|
|
|
### Icon Format Examples
|
|
|
|
```json
|
|
// Font Awesome
|
|
"icon": "fas fa-star"
|
|
"icon": "far fa-heart"
|
|
"icon": "fab fa-twitter"
|
|
|
|
// Emoji
|
|
"icon": "⭐"
|
|
"icon": "❤️"
|
|
"icon": "🐦"
|
|
|
|
// Custom Image
|
|
"icon": "/plugins/my-plugin/icon.png"
|
|
"icon": "https://example.com/logo.svg"
|
|
```
|
|
|
|
## Browse Available Icons
|
|
|
|
- **Font Awesome:** [fontawesome.com/icons](https://fontawesome.com/icons) (Free tier includes 2,000+ icons)
|
|
- **Emojis:** [unicode.org/emoji](https://unicode.org/emoji/charts/full-emoji-list.html)
|
|
|
|
## Best Practices
|
|
|
|
1. **Choose meaningful icons** - Icon should relate to plugin function
|
|
2. **Keep it simple** - Works better at small sizes
|
|
3. **Test visibility** - Ensure icon is clear at 16px
|
|
4. **Match UI style** - Font Awesome recommended for consistency
|
|
5. **Document choice** - Note icon meaning in plugin README
|
|
|
|
## Troubleshooting
|
|
|
|
**Icon not showing?**
|
|
- Check manifest syntax (JSON valid?)
|
|
- Verify icon field spelling
|
|
- Refresh plugins in web interface
|
|
- Check browser console for errors
|
|
|
|
**Wrong icon appearing?**
|
|
- Font Awesome: Verify class name at fontawesome.com
|
|
- Emoji: Try different emoji (platform rendering varies)
|
|
- Custom image: Check file path and permissions
|
|
|
|
## Future Enhancements
|
|
|
|
Possible future improvements:
|
|
- Icon picker in plugin store
|
|
- Animated icons support
|
|
- SVG path support
|
|
- Icon themes/styles
|
|
- Dynamic icon changes based on state
|
|
|
|
## Summary
|
|
|
|
**Mission accomplished!** 🎉
|
|
|
|
Plugins can now have custom icons by adding one line to their manifest:
|
|
|
|
```json
|
|
"icon": "fas fa-your-icon"
|
|
```
|
|
|
|
Three formats supported:
|
|
- ✅ Font Awesome (professional)
|
|
- ✅ Emoji (fun)
|
|
- ✅ Custom images (branded)
|
|
|
|
The feature is:
|
|
- ✅ Easy to use (one line)
|
|
- ✅ Flexible (three options)
|
|
- ✅ Backward compatible
|
|
- ✅ Well documented
|
|
- ✅ Already working in example plugins
|
|
|
|
**Ready to use!** 🚀
|
|
|