From fd78cecf4a179294fc9822e7a393533f59f0883b Mon Sep 17 00:00:00 2001 From: Ruslan Date: Fri, 10 Jul 2026 17:21:30 +0300 Subject: [PATCH] UI: Add export/import and copy/paste of widget actions Adds the ability to transfer widget actions instead of recreating them by hand (feature request thingsboard/thingsboard#14986, and per-action duplicate #11907): - Export all actions or a single action to a JSON file; import merges them into the target widget's actions, dropping sources/types the widget does not support, regenerating ids, de-duplicating names and resetting invalid cellClick column bindings, with a summary toast. - Copy all / copy single action to a clipboard buffer (ItemBufferService) and paste onto another widget on any dashboard/tab; duplicate an action in place. - Clear all actions. Transfer/bulk operations are grouped under a toolbar overflow menu; per-row copy/duplicate/export under a row overflow menu. Import also supports pasting raw JSON content in the import dialog. --- .../app/core/services/item-buffer.service.ts | 14 ++ ...anage-widget-actions-dialog.component.html | 1 + .../manage-widget-actions.component.html | 68 +++++++- .../manage-widget-actions.component.models.ts | 71 +++++++- .../action/manage-widget-actions.component.ts | 152 +++++++++++++++++- .../widget/widget-config.component.html | 1 + .../widget/widget-config.component.ts | 2 +- .../import-export/import-export.service.ts | 39 ++++- ui-ngx/src/app/shared/models/widget.models.ts | 4 +- .../assets/locale/locale.constant-en_US.json | 19 +++ 10 files changed, 360 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts index 1dc7146883..0c933d1213 100644 --- a/ui-ngx/src/app/core/services/item-buffer.service.ts +++ b/ui-ngx/src/app/core/services/item-buffer.service.ts @@ -28,6 +28,7 @@ import { DatasourceType, TargetDeviceType, Widget, + WidgetActionsMap, WidgetPosition, WidgetSize, widgetType @@ -45,6 +46,7 @@ import { findWidgetModelDefinition } from '@shared/models/widget/widget-model.de const WIDGET_ITEM = 'widget_item'; const WIDGET_REFERENCE = 'widget_reference'; +const WIDGET_ACTIONS = 'widget_actions'; const RULE_NODES = 'rule_nodes'; const RULE_CHAIN_IMPORT = 'rule_chain_import'; @@ -180,6 +182,18 @@ export class ItemBufferService { return this.storeHas(WIDGET_ITEM); } + public copyWidgetActions(actionsMap: WidgetActionsMap): void { + this.storeSet(WIDGET_ACTIONS, actionsMap); + } + + public hasWidgetActions(): boolean { + return this.storeHas(WIDGET_ACTIONS); + } + + public pasteWidgetActions(): WidgetActionsMap { + return this.storeGet(WIDGET_ACTIONS); + } + public canPasteWidgetReference(dashboard: Dashboard, state: string, layout: DashboardLayoutId, breakpoint: string): boolean { const widgetReference: WidgetReference = this.storeGet(WIDGET_REFERENCE); if (widgetReference) { diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html index 2a83f7d1c5..dde39a2fb9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html @@ -35,6 +35,7 @@ widget-config.actions + + + + + + + + + + + + + @if (actionSources[action.actionSourceId]?.multiple) { + + } + +