mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 08:48:05 +00:00
feat(web): plugin install auto-enables + persistent restart nudge
Getting a plugin onto the display used to take three disconnected manual
steps: install from the store, flip its enable toggle, then restart the
display service - with no in-UI hint that steps 2 and 3 were needed (only
docs/GETTING_STARTED.md mentions it).
- installPlugin() now enables the plugin immediately on successful install
(owner-confirmed behavior change: always auto-enable, no opt-out; users
who don't want it running toggle it off as before), then shows a
persistent toast ("... restart the display to show it") with an inline
"Restart Now" button wired to the existing restartDisplay() - the same
function the three existing Restart Display buttons call.
- notification.js: show() accepts optional { actionLabel, onAction } to
render one inline action button per toast. Callbacks are stored per
notification id and cleaned up on dismiss; a new triggerAction() public
method runs the callback and dismisses. The global showNotification()
shorthand now forwards a full options object as its second argument
(legacy type-string calls unchanged).
Scope note: applies to the plugin store's install path (window.installPlugin).
The custom-registry install path keeps its existing behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
af991d4cca
commit
242cd2a943
@@ -58,6 +58,9 @@
|
|||||||
|
|
||||||
// Track active notifications
|
// Track active notifications
|
||||||
let activeNotifications = [];
|
let activeNotifications = [];
|
||||||
|
// onAction callbacks for notifications with an inline action button,
|
||||||
|
// keyed by notification id (cleaned up on dismiss).
|
||||||
|
const actionCallbacks = {};
|
||||||
let notificationCounter = 0;
|
let notificationCounter = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,6 +116,7 @@
|
|||||||
|
|
||||||
// Remove from tracking array
|
// Remove from tracking array
|
||||||
activeNotifications = activeNotifications.filter(id => id !== notificationId);
|
activeNotifications = activeNotifications.filter(id => id !== notificationId);
|
||||||
|
delete actionCallbacks[notificationId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,6 +162,20 @@
|
|||||||
|
|
||||||
html += `<span class="flex-1 text-sm">${escapeHtml(message)}</span>`;
|
html += `<span class="flex-1 text-sm">${escapeHtml(message)}</span>`;
|
||||||
|
|
||||||
|
// Optional inline action button (e.g. "Restart Now" on a restart nudge).
|
||||||
|
// The callback is stored by id and invoked via triggerAction, which
|
||||||
|
// also dismisses the notification.
|
||||||
|
if (options.actionLabel && typeof options.onAction === 'function') {
|
||||||
|
actionCallbacks[notificationId] = options.onAction;
|
||||||
|
html += `
|
||||||
|
<button type="button"
|
||||||
|
onclick="window.LEDMatrixWidgets.get('notification').triggerAction('${notificationId}')"
|
||||||
|
class="flex-shrink-0 ml-2 px-3 py-1 text-xs font-semibold rounded-md bg-white bg-opacity-20 hover:bg-opacity-30 transition-colors duration-150">
|
||||||
|
${escapeHtml(options.actionLabel)}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
if (dismissible) {
|
if (dismissible) {
|
||||||
html += `
|
html += `
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@@ -227,6 +245,17 @@
|
|||||||
removeNotification(notificationId);
|
removeNotification(notificationId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a notification's onAction callback (see options.actionLabel /
|
||||||
|
* options.onAction on show) and dismiss it.
|
||||||
|
* @param {string} notificationId - Notification ID whose action to run
|
||||||
|
*/
|
||||||
|
triggerAction: function(notificationId) {
|
||||||
|
const cb = actionCallbacks[notificationId];
|
||||||
|
removeNotification(notificationId);
|
||||||
|
if (cb) cb();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all notifications
|
* Clear all notifications
|
||||||
*/
|
*/
|
||||||
@@ -262,9 +291,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Global shorthand function (backwards compatible with existing code)
|
// Global shorthand function (backwards compatible with existing code).
|
||||||
|
// Accepts either the legacy type string or a full options object
|
||||||
|
// ({ type, duration, actionLabel, onAction, ... }).
|
||||||
window.showNotification = function(message, type = 'info') {
|
window.showNotification = function(message, type = 'info') {
|
||||||
return showNotification(message, { type: type });
|
return showNotification(message, typeof type === 'string' ? { type: type } : (type || {}));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize container on load
|
// Initialize container on load
|
||||||
|
|||||||
@@ -3888,6 +3888,19 @@ window.installPlugin = function(pluginId, branch = null) {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
showNotification(data.message, data.status);
|
showNotification(data.message, data.status);
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
|
// Enable immediately so install -> enable is one step, then nudge
|
||||||
|
// for the display restart that's needed before it shows on the
|
||||||
|
// matrix (persistent toast; duration 0 = stays until dismissed).
|
||||||
|
window.togglePlugin(pluginId, true);
|
||||||
|
showNotification(
|
||||||
|
`${pluginId} installed and enabled — restart the display to show it`,
|
||||||
|
{
|
||||||
|
type: 'success',
|
||||||
|
duration: 0,
|
||||||
|
actionLabel: 'Restart Now',
|
||||||
|
onAction: () => restartDisplay()
|
||||||
|
}
|
||||||
|
);
|
||||||
// Refresh installed plugins list, then re-render store to update badges
|
// Refresh installed plugins list, then re-render store to update badges
|
||||||
loadInstalledPlugins();
|
loadInstalledPlugins();
|
||||||
setTimeout(() => applyStoreFiltersAndSort(true), 500);
|
setTimeout(() => applyStoreFiltersAndSort(true), 500);
|
||||||
|
|||||||
Reference in New Issue
Block a user