From a1315024c5a672ebcf28981ba0cd1b749306252a Mon Sep 17 00:00:00 2001 From: librelad Date: Wed, 27 May 2026 15:05:02 +0100 Subject: [PATCH] ui(tasks): delete notification matches the started/completed format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Task deleted successfully" was a plain single-line toast while every other task notification (started, completed, failed, cancelled) renders as -in-bold on the first line + " task " on the second, with the app icon on the left and the per-action emoji as custom-icon. Inconsistent. Now reads e.g. [🗑️] Ipinfo Install task deleted. with the ipinfo logo as the row icon, matching the install/completion toast format. Also factored the three duplicate "task → identity (display name + app icon + friendly action title + emoji)" blocks (taskCompleted listener, delete-modal title, delete notification) into one helper — _taskNotificationDescriptor(task) — so the four surfaces (started, completed/failed/cancelled, delete modal, delete notification) always agree on what to call a task. Net -20 lines. Signed-off-by: librelad --- .../js/components/tasks/tasks-manager.js | 126 ++++++++---------- 1 file changed, 53 insertions(+), 73 deletions(-) diff --git a/containers/libreportal/frontend/js/components/tasks/tasks-manager.js b/containers/libreportal/frontend/js/components/tasks/tasks-manager.js index bf145c0..395b824 100755 --- a/containers/libreportal/frontend/js/components/tasks/tasks-manager.js +++ b/containers/libreportal/frontend/js/components/tasks/tasks-manager.js @@ -78,6 +78,38 @@ class TasksManager { return action.split(/[_-]/).map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' '); } + // Shared "task → notification payload" resolver. Every task surface + // (started/completed toast, delete modal, delete confirmation) builds + // the same identity (display name, app icon, friendly action title, + // emoji type-icon) — keep it in one place so they read consistently. + _taskNotificationDescriptor(task) { + const appName = (task && task.app) || null; + const action = (task && task.type) || ''; + const isSystemTask = action.startsWith('setup-') || appName === 'system'; + let actionTitle = this.formatActionTitle(action); + // Tool tasks: prefer the catalog-defined label. + const toolCmdMatch = ((task && task.command) || '').match(/libreportal app tool (\S+) (\S+)/); + if (toolCmdMatch) { + const toolId = toolCmdMatch[2]; + let toolLabel = null; + const cat = window.toolsCatalog; + if (cat && cat.apps && cat.apps[toolCmdMatch[1]] && Array.isArray(cat.apps[toolCmdMatch[1]].tools)) { + const t = cat.apps[toolCmdMatch[1]].tools.find(x => x.id === toolId); + if (t && t.label) toolLabel = t.label; + } + if (!toolLabel) toolLabel = toolId.split(/[_-]/).map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' '); + actionTitle = toolLabel; + } + const displayName = isSystemTask + ? 'LibrePortal' + : ((appName && window.getAppDisplayName) ? window.getAppDisplayName(appName) : (appName || '')); + const icon = isSystemTask + ? '/icons/libreportal.svg' + : (appName ? `/icons/apps/${encodeURIComponent(appName)}.svg` : null); + const typeIcon = (this.getTaskTypeIcon ? this.getTaskTypeIcon(task)?.icon : '') || ''; + return { appName, isSystemTask, actionTitle, displayName, icon, typeIcon }; + } + // Initialize task system after scripts are loaded initializeTaskSystem() { try { @@ -1402,49 +1434,13 @@ class TasksManager { // Layout matches the " task started!" format used by task-actions // and backup-manager so started/completed look like a matched pair. if (window.notificationSystem && task) { - const appName = task.app || null; - const action = task.type || 'task'; - let actionTitle = this.formatActionTitle(action); - // Tool tasks: override the generic "Tool" label with the tool's - // friendly name (e.g. "Manage Shortcuts") so completion toasts - // match what the user clicked. - const toolCmdMatch = (task.command || '').match(/libreportal app tool (\S+) (\S+)/); - if (toolCmdMatch) { - const toolApp = toolCmdMatch[1]; - const toolId = toolCmdMatch[2]; - let toolLabel = null; - const cat = window.toolsCatalog; - if (cat && cat.apps && cat.apps[toolApp] && Array.isArray(cat.apps[toolApp].tools)) { - const t = cat.apps[toolApp].tools.find(x => x.id === toolId); - if (t && t.label) toolLabel = t.label; - } - if (!toolLabel) { - toolLabel = toolId.split('_').map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' '); - } - actionTitle = toolLabel; - } - // System-level tasks resolve to LibrePortal as the subject + use the - // LibrePortal logo as the app-icon. Covers `app: 'system'` (config - // update, system update) and `setup-*` types (setup wizard phases). - const isSystemTask = action.startsWith('setup-') || appName === 'system'; - const displayName = isSystemTask - ? 'LibrePortal' - : ((appName && window.getAppDisplayName) - ? window.getAppDisplayName(appName) - : (appName || (task.command || `Task ${taskId}`))); + const { appName, actionTitle, displayName, icon, typeIcon } = this._taskNotificationDescriptor(task); const onAppPage = window.location.pathname.startsWith('/app') && !window.location.pathname.startsWith('/apps'); const url = (onAppPage && appName) ? window.appPath(appName, 'tasks', null, taskId) : `/tasks/all?task=${taskId}`; - const icon = isSystemTask - ? '/icons/libreportal.svg' - : (appName ? `/icons/apps/${appName}.svg` : null); - - // Match the per-action emoji used in the task list rows (see - // `getTaskTypeIcon`). Passed as the 6th `customIcon` arg so the - // notification's leftmost icon slot shows the task *type* (install - // ✅, backup 💾, restore 📦, …) instead of the generic level tick. - const typeIcon = (this.getTaskTypeIcon ? this.getTaskTypeIcon(task) : null)?.icon || ''; + // Per-action emoji (install ✅, backup 💾, restore 📦, …) in the + // notification's leftmost icon slot, mirroring task-list rows. const customIcon = typeIcon ? `${typeIcon}` : null; let body; @@ -2196,11 +2192,16 @@ class TasksManager { this.generateAppCategories(); if (window.notificationSystem) { - // Type icon from whatever the task was, with a trash fallback - // because deletion is conceptually a 🗑️ action. - const typeIcon = (task && this.getTaskTypeIcon ? this.getTaskTypeIcon(task)?.icon : '') || '🗑️'; - const customIcon = `${typeIcon}`; - window.notificationSystem.show('Task deleted successfully', 'info', null, null, null, customIcon); + // Match the " task " two-line shape used by started/ + // completed/failed/cancelled so deletion reads as part of the same + // family. Type emoji falls back to 🗑️ since deletion is a 🗑️ action. + const { appName, actionTitle, displayName, icon, typeIcon } = this._taskNotificationDescriptor(task); + const emoji = typeIcon || '🗑️'; + const customIcon = `${emoji}`; + const body = displayName + ? `${displayName}
${actionTitle} task deleted.` + : `${actionTitle || 'Task'} deleted.`; + window.notificationSystem.show(body, 'info', appName, null, icon, customIcon); } } catch (error) { console.error('Error deleting task:', error); @@ -2219,36 +2220,15 @@ class TasksManager { const escHtml = (s) => String(s == null ? '' : s) .replace(/&/g, '&').replace(//g, '>'); - // Build a friendly title + app icon so the modal reads "Install Ipinfo" - // with the app's logo, not the raw "libreportal app install ipinfo" - // command. Mirrors the completion-notification flow above so the - // visual identity of a task is consistent across surfaces. - const appName = (task && task.app) || null; - const action = (task && task.type) || ''; - const isSystemTask = action.startsWith('setup-') || appName === 'system'; - let actionTitle = this.formatActionTitle(action); - // Tool tasks: prefer the tool's curated label. - const toolCmdMatch = (task && task.command || '').match(/libreportal app tool (\S+) (\S+)/); - if (toolCmdMatch) { - const toolId = toolCmdMatch[2]; - let toolLabel = null; - const cat = window.toolsCatalog; - if (cat && cat.apps && cat.apps[toolCmdMatch[1]] && Array.isArray(cat.apps[toolCmdMatch[1]].tools)) { - const t = cat.apps[toolCmdMatch[1]].tools.find(x => x.id === toolId); - if (t && t.label) toolLabel = t.label; - } - if (!toolLabel) toolLabel = toolId.split(/[_-]/).map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' '); - actionTitle = toolLabel; - } - const displayName = isSystemTask - ? '' - : ((appName && window.getAppDisplayName) ? window.getAppDisplayName(appName) : (appName || '')); - const taskLabel = displayName - ? `${actionTitle} ${displayName}` + // Friendly title + app icon — mirrors the completion-toast format so + // the modal's identity matches every other surface. + const { isSystemTask, actionTitle, displayName, icon: taskIcon } = this._taskNotificationDescriptor(task); + // For the modal we omit the "LibrePortal" subject (the eyebrow already + // says "Delete Task") and just lead with the action: "Update Config". + const modalSubject = isSystemTask ? '' : displayName; + const taskLabel = modalSubject + ? `${actionTitle} ${modalSubject}` : (actionTitle || (task && task.id) || 'Unknown task'); - const taskIcon = isSystemTask - ? '/icons/libreportal.svg' - : (appName ? `/icons/apps/${encodeURIComponent(appName)}.svg` : null); const taskStatus = (task && task.status) || 'unknown'; const warningTitle = isActive ? 'Active task' : 'This cannot be undone';