Dashboards: undo redo move tab (#106786)
* move tab Fixed issue * make i18 * always select the tab after moving it, select it after undo too
This commit is contained in:
@@ -122,6 +122,10 @@ export class DashboardEditPane extends SceneObjectBase<DashboardEditPaneState> {
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
if (action.movedObject) {
|
||||
this.selectObject(action.movedObject, action.movedObject.state.key!, { force: true });
|
||||
}
|
||||
|
||||
if (action.removedObject) {
|
||||
this.newObjectAddedToCanvas(action.removedObject);
|
||||
}
|
||||
@@ -139,6 +143,10 @@ export class DashboardEditPane extends SceneObjectBase<DashboardEditPaneState> {
|
||||
this.newObjectAddedToCanvas(action.addedObject);
|
||||
}
|
||||
|
||||
if (action.movedObject) {
|
||||
this.selectObject(action.movedObject, action.movedObject.state.key!, { force: true });
|
||||
}
|
||||
|
||||
if (action.removedObject) {
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export class ConditionalRenderingChangedEvent extends BusEventWithPayload<SceneO
|
||||
export interface DashboardEditActionEventPayload {
|
||||
removedObject?: SceneObject;
|
||||
addedObject?: SceneObject;
|
||||
movedObject?: SceneObject;
|
||||
source: SceneObject;
|
||||
description?: string;
|
||||
perform: () => void;
|
||||
@@ -128,6 +129,13 @@ export interface ChangeDescriptionActionHelperProps {
|
||||
source: DashboardScene;
|
||||
}
|
||||
|
||||
export interface MoveElementActionHelperProps {
|
||||
movedObject: SceneObject;
|
||||
source: SceneObject;
|
||||
perform: () => void;
|
||||
undo: () => void;
|
||||
}
|
||||
|
||||
export const dashboardEditActions = {
|
||||
/**
|
||||
* Registers and peforms an edit action
|
||||
@@ -230,6 +238,25 @@ export const dashboardEditActions = {
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
moveElement(props: MoveElementActionHelperProps) {
|
||||
const { movedObject, source, perform, undo } = props;
|
||||
|
||||
const element = getEditableElementFor(movedObject);
|
||||
if (!element) {
|
||||
throw new Error('Moved object is not an editable element');
|
||||
}
|
||||
|
||||
const typeName = element.getEditableElementInfo().typeName;
|
||||
|
||||
dashboardEditActions.edit({
|
||||
description: t('dashboard.edit-actions.move', 'Move {{typeName}}', { typeName }),
|
||||
movedObject,
|
||||
source,
|
||||
perform,
|
||||
undo,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export function undoRedoWasClicked(e: React.FocusEvent) {
|
||||
|
||||
@@ -17,7 +17,12 @@ jest.mock('../../edit-pane/shared', () => ({
|
||||
perform();
|
||||
lastUndo = undo;
|
||||
}),
|
||||
moveElement: jest.fn(({ perform, undo }) => {
|
||||
perform();
|
||||
lastUndo = undo;
|
||||
}),
|
||||
},
|
||||
ObjectsReorderedOnCanvasEvent: jest.fn().mockImplementation(() => ({})),
|
||||
}));
|
||||
|
||||
describe('TabsLayoutManager', () => {
|
||||
@@ -179,4 +184,66 @@ describe('TabsLayoutManager', () => {
|
||||
expect(manager.state.currentTabIndex).toBe(1); // tab2 should be current again
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveTab', () => {
|
||||
beforeEach(() => {
|
||||
lastUndo = undefined;
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should move a tab to a new position', () => {
|
||||
const manager = new TabsLayoutManager({ tabs: [] });
|
||||
const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
|
||||
const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));
|
||||
const tab3 = manager.addNewTab(new TabItem({ title: 'Tab 3' }));
|
||||
|
||||
expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);
|
||||
|
||||
manager.moveTab(0, 2);
|
||||
|
||||
expect(manager.state.tabs).toEqual([tab2, tab3, tab1]);
|
||||
expect(dashboardEditActions.moveElement).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle undo action correctly when moving a tab', () => {
|
||||
const manager = new TabsLayoutManager({ tabs: [] });
|
||||
const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
|
||||
const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));
|
||||
const tab3 = manager.addNewTab(new TabItem({ title: 'Tab 3' }));
|
||||
|
||||
expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);
|
||||
|
||||
manager.moveTab(0, 2);
|
||||
|
||||
expect(manager.state.tabs).toEqual([tab2, tab3, tab1]);
|
||||
|
||||
// Use the real undo function from the mock
|
||||
expect(typeof lastUndo).toBe('function');
|
||||
lastUndo && lastUndo();
|
||||
|
||||
expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);
|
||||
});
|
||||
|
||||
it('should update currentTabIndex when moving the current tab', () => {
|
||||
const manager = new TabsLayoutManager({ tabs: [] });
|
||||
const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
|
||||
const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));
|
||||
const tab3 = manager.addNewTab(new TabItem({ title: 'Tab 3' }));
|
||||
|
||||
// Set tab2 as current
|
||||
manager.setState({ currentTabIndex: 1 });
|
||||
expect(manager.state.currentTabIndex).toBe(1);
|
||||
|
||||
manager.moveTab(1, 0);
|
||||
|
||||
expect(manager.state.tabs).toEqual([tab2, tab1, tab3]);
|
||||
expect(manager.state.currentTabIndex).toBe(0);
|
||||
|
||||
// Undo should restore the original state
|
||||
lastUndo && lastUndo();
|
||||
|
||||
expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);
|
||||
expect(manager.state.currentTabIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -230,7 +230,22 @@ export class TabsLayoutManager extends SceneObjectBase<TabsLayoutManagerState> i
|
||||
this.setState({ tabs, currentTabIndex: 0 });
|
||||
}
|
||||
|
||||
public moveTab(_tabKey: string, fromIndex: number, toIndex: number) {
|
||||
public moveTab(fromIndex: number, toIndex: number) {
|
||||
const objectToMove = this.state.tabs[fromIndex];
|
||||
|
||||
dashboardEditActions.moveElement({
|
||||
source: this,
|
||||
movedObject: objectToMove,
|
||||
perform: () => {
|
||||
this.rearrangeTabs(fromIndex, toIndex);
|
||||
},
|
||||
undo: () => {
|
||||
this.rearrangeTabs(toIndex, fromIndex);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private rearrangeTabs(fromIndex: number, toIndex: number) {
|
||||
const tabs = [...this.state.tabs];
|
||||
const [removed] = tabs.splice(fromIndex, 1);
|
||||
tabs.splice(toIndex, 0, removed);
|
||||
|
||||
@@ -38,7 +38,7 @@ export function TabsLayoutManagerRenderer({ model }: SceneComponentProps<TabsLay
|
||||
return;
|
||||
}
|
||||
|
||||
model.moveTab(result.draggableId, result.source.index, result.destination.index);
|
||||
model.moveTab(result.source.index, result.destination.index);
|
||||
}}
|
||||
>
|
||||
<div className={styles.tabsRow}>
|
||||
|
||||
@@ -4055,6 +4055,7 @@
|
||||
},
|
||||
"edit-actions": {
|
||||
"add": "Add {{typeName}}",
|
||||
"move": "Move {{typeName}}",
|
||||
"panel-background": "Change panel background",
|
||||
"panel-description": "Change panel description",
|
||||
"panel-title": "Change panel title",
|
||||
|
||||
Reference in New Issue
Block a user