diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index 1f3216dea99..f8cb979f064 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -186,6 +186,29 @@ export class DefaultGridLayoutManager this.publishEvent(new NewObjectAddedToCanvasEvent(newPanel), true); } + public duplicate(): DashboardLayoutManager { + const clone = this.clone({ + key: undefined, + grid: this.state.grid.clone({ + key: undefined, + children: this.state.grid.state.children.map((child) => { + if (child instanceof DashboardGridItem) { + return child.clone({ + key: undefined, + body: child.state.body.clone({ + key: getVizPanelKeyForPanelId(dashboardSceneGraph.getNextPanelId(child.state.body)), + }), + }); + } + + return child.clone({ key: undefined }); + }), + }), + }); + + return clone; + } + public getVizPanels(): VizPanel[] { const panels: VizPanel[] = []; diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index 8130cecec00..fe17f5ca802 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -72,6 +72,27 @@ export class ResponsiveGridLayoutManager this.publishEvent(new ObjectRemovedFromCanvasEvent(panel), true); } + public duplicate(): DashboardLayoutManager { + return this.clone({ + key: undefined, + layout: this.state.layout.clone({ + key: undefined, + children: this.state.layout.state.children.map((child) => { + if (child instanceof ResponsiveGridItem) { + return child.clone({ + key: undefined, + body: child.state.body.clone({ + key: getVizPanelKeyForPanelId(dashboardSceneGraph.getNextPanelId(child.state.body)), + }), + }); + } + + return child.clone({ key: undefined }); + }), + }), + }); + } + public duplicatePanel(panel: VizPanel) { const gridItem = panel.parent; if (!(gridItem instanceof ResponsiveGridItem)) { diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx index d85eb832757..8d3b8f91a75 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx @@ -88,6 +88,14 @@ export class RowItem return new RowItems(items.filter((item) => item instanceof RowItem)); } + public onDuplicate() { + this._getParentLayout().duplicateRow(this); + } + + public duplicate(): RowItem { + return this.clone({ key: undefined, layout: this.getLayout().duplicate() }); + } + public onAddPanel(panel = getDefaultVizPanel()) { this.getLayout().addPanel(panel); } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 28b1960c285..7ad15344c45 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -75,6 +75,17 @@ export class RowsLayoutManager extends SceneObjectBase i throw new Error('Method not implemented.'); } + public duplicate(): DashboardLayoutManager { + const newRows = this.state.rows.map((row) => row.duplicate()); + return this.clone({ rows: newRows, key: undefined }); + } + + public duplicateRow(row: RowItem) { + const newRow = row.duplicate(); + this.setState({ rows: [...this.state.rows, newRow] }); + this.publishEvent(new NewObjectAddedToCanvasEvent(newRow), true); + } + public addNewRow(): RowItem { const row = new RowItem(); this.setState({ rows: [...this.state.rows, row] }); diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx index e32383f9eaa..df09763b00b 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx @@ -70,6 +70,14 @@ export class TabItem return new TabItems(items.filter((item) => item instanceof TabItem)); } + public onDuplicate(): void { + this._getParentLayout().duplicateTab(this); + } + + public duplicate(): TabItem { + return this.clone({ key: undefined, layout: this.getLayout().duplicate() }); + } + public onAddPanel(panel = getDefaultVizPanel()) { this.getLayout().addPanel(panel); } diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index a0296b4c2b5..6478ff19c54 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -54,6 +54,16 @@ export class TabsLayoutManager extends SceneObjectBase i }); } + public duplicate(): DashboardLayoutManager { + // Maybe not needed, depending on if we want nested tabs or tabs within rows + throw new Error('Method not implemented.'); + } + + public duplicateTab(tab: TabItem) { + const newTab = tab.duplicate(); + this.setState({ tabs: [...this.state.tabs, newTab] }); + } + public getUrlState() { return { tab: this.state.currentTabIndex.toString() }; } diff --git a/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts b/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts index 190a6806467..1065b592f67 100644 --- a/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts +++ b/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts @@ -66,6 +66,11 @@ export interface DashboardLayoutManager extends SceneObject { * @param isSource */ cloneLayout(ancestorKey: string, isSource: boolean): DashboardLayoutManager; + + /** + * Duplicate, like clone but with new keys + */ + duplicate(): DashboardLayoutManager; } export interface LayoutManagerSerializer {