mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
PLUGIN_ARCHITECTURE_SPEC.md
- Added a banner at the top noting this is a historical design doc
written before the plugin system shipped. The doc is ~1900 lines
with 13 stale /api/plugins/* paths (real is /api/v3/plugins/*),
references to web_interface_v2.py (current is app.py), and a
Migration Strategy / Implementation Roadmap that's now history.
Banner points readers at the current docs
(PLUGIN_DEVELOPMENT_GUIDE, PLUGIN_API_REFERENCE,
REST_API_REFERENCE) without needing to retrofit every section.
PLUGIN_CONFIG_ARCHITECTURE.md
- 10 occurrences of /api/plugins/* missing /v3 prefix. Bulk fixed.
DEVELOPER_QUICK_REFERENCE.md
- cache_manager.delete("key") -> cache_manager.clear_cache("key")
with comment noting delete() doesn't exist. Same bug already
documented in PLUGIN_API_REFERENCE.md.
SSH_UNAVAILABLE_AFTER_INSTALL.md
- 4 occurrences of port 5001 -> 5000 in AP-mode and Ethernet/WiFi
recovery instructions.
PLUGIN_CUSTOM_ICONS_FEATURE.md
- Port 5001 -> 5000.
CONFIG_DEBUGGING.md
- Documented /api/v3/config/plugin/<id> and /api/v3/config/validate
endpoints don't exist. Replaced with the real endpoints:
/api/v3/config/main, /api/v3/plugins/schema?plugin_id=,
/api/v3/plugins/config?plugin_id=. Added a note that validation
runs server-side automatically on POST.
STARLARK_APPS_GUIDE.md
- "Plugins -> Starlark Apps" UI navigation path doesn't exist (5
occurrences). Replaced with the real path: Plugin Manager tab,
then the per-plugin Starlark Apps tab in the second nav row.
- "Navigate to Plugins" install step -> Plugin Manager tab.
web_interface/README.md
- Documented several endpoints that don't exist in the api_v3
blueprint:
- GET /api/v3/plugins (list) -> /api/v3/plugins/installed
- GET /api/v3/plugins/<id> -> doesn't exist
- POST /api/v3/plugins/<id>/config -> POST /api/v3/plugins/config
- GET /api/v3/plugins/<id>/enable + /disable -> POST /api/v3/plugins/toggle
- GET /api/v3/store/plugins -> /api/v3/plugins/store/list
- POST /api/v3/store/install/<id> -> POST /api/v3/plugins/install
- POST /api/v3/store/uninstall/<id> -> POST /api/v3/plugins/uninstall
- POST /api/v3/store/update/<id> -> POST /api/v3/plugins/update
- POST /api/v3/display/start/stop/restart -> POST /api/v3/system/action
- GET /api/v3/display/status -> GET /api/v3/system/status
- Also fixed config/secrets.json -> config/config_secrets.json
- Replaced the per-section endpoint duplication with a current real
endpoint list and a pointer to docs/REST_API_REFERENCE.md.
- Documented that SSE stream endpoints are defined directly on the
Flask app at app.py:607-615, not in the api_v3 blueprint.
scripts/install/README.md
- Was missing 3 of the 9 install scripts in the directory:
one-shot-install.sh, configure_wifi_permissions.sh, and
debug_install.sh. Added them with brief descriptions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
427 lines
11 KiB
Markdown
427 lines
11 KiB
Markdown
# ✅ Plugin Custom Icons Feature - Complete
|
|
|
|
## 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!** 🚀
|
|
|