Dashboards: Add undo/redo actions for changing dashboard title (#106574)
* Dashboards: Add undo/redo actions for changing dashboard title * Run make i18n-extract * Prevent empty undo/redo actions
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { ReactNode, useMemo, useRef } from 'react';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Input, TextArea } from '@grafana/ui';
|
||||
@@ -7,9 +7,12 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { useLayoutCategory } from '../scene/layouts-shared/DashboardLayoutSelector';
|
||||
import { redoButtonId, undoButtonID } from '../scene/new-toolbar/RightActions';
|
||||
import { EditSchemaV2Button } from '../scene/new-toolbar/actions/EditSchemaV2Button';
|
||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||
|
||||
import { dashboardEditActions } from './shared';
|
||||
|
||||
export class DashboardEditableElement implements EditableDashboardElement {
|
||||
public readonly isEditableDashboardElement = true;
|
||||
|
||||
@@ -79,7 +82,38 @@ export class DashboardEditableElement implements EditableDashboardElement {
|
||||
export function DashboardTitleInput({ dashboard, id }: { dashboard: DashboardScene; id?: string }) {
|
||||
const { title } = dashboard.useState();
|
||||
|
||||
return <Input id={id} value={title} onChange={(e) => dashboard.setState({ title: e.currentTarget.value })} />;
|
||||
// We want to save the unchanged value for the 'undo' action
|
||||
const valueBeforeEdit = useRef('');
|
||||
|
||||
return (
|
||||
<Input
|
||||
id={id}
|
||||
value={title}
|
||||
onChange={(e) => {
|
||||
dashboard.setState({ title: e.currentTarget.value });
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
valueBeforeEdit.current = e.currentTarget.value;
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
// If the title input is currently focused and we click undo/redo
|
||||
// we don't want to mess with the stack
|
||||
const clickedUndoRedo =
|
||||
e.relatedTarget && (e.relatedTarget.id === undoButtonID || e.relatedTarget.id === redoButtonId);
|
||||
const titleUnchanged = valueBeforeEdit.current === e.currentTarget.value;
|
||||
const shouldSkip = titleUnchanged || clickedUndoRedo;
|
||||
if (shouldSkip) {
|
||||
return;
|
||||
}
|
||||
|
||||
dashboardEditActions.changeTitle({
|
||||
source: dashboard,
|
||||
oldTitle: valueBeforeEdit.current,
|
||||
newTitle: e.currentTarget.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DashboardDescriptionInput({ dashboard, id }: { dashboard: DashboardScene; id?: string }) {
|
||||
|
||||
@@ -98,17 +98,23 @@ export interface RemoveElementActionHelperProps {
|
||||
undo: () => void;
|
||||
}
|
||||
|
||||
export interface ChangeTitleActionHelperProps {
|
||||
oldTitle: string;
|
||||
newTitle: string;
|
||||
source: DashboardScene;
|
||||
}
|
||||
|
||||
export const dashboardEditActions = {
|
||||
/**
|
||||
* Registers and peforms an edit action
|
||||
*/
|
||||
edit: function (props: DashboardEditActionEventPayload) {
|
||||
edit(props: DashboardEditActionEventPayload) {
|
||||
props.source.publishEvent(new DashboardEditActionEvent(props), true);
|
||||
},
|
||||
/**
|
||||
* Helper for makeEdit that adds elements
|
||||
*/
|
||||
addElement: function (props: AddElementActionHelperProps) {
|
||||
addElement(props: AddElementActionHelperProps) {
|
||||
const { addedObject, source, perform, undo } = props;
|
||||
|
||||
const element = getEditableElementFor(addedObject);
|
||||
@@ -145,4 +151,17 @@ export const dashboardEditActions = {
|
||||
undo,
|
||||
});
|
||||
},
|
||||
|
||||
changeTitle({ source, oldTitle, newTitle }: ChangeTitleActionHelperProps) {
|
||||
dashboardEditActions.edit({
|
||||
description: t('dashboard.title.action', 'Change dashboard title'),
|
||||
source: source,
|
||||
perform: () => {
|
||||
source.setState({ title: newTitle });
|
||||
},
|
||||
undo: () => {
|
||||
source.setState({ title: oldTitle });
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -165,14 +165,16 @@ export const RightActions = ({ dashboard }: { dashboard: DashboardScene }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const undoButtonID = 'undo-button';
|
||||
function UndoButton({ dashboard }: ToolbarActionProps) {
|
||||
const editPane = dashboard.state.editPane;
|
||||
const { undoStack } = editPane.useState();
|
||||
const undoAction = undoStack[undoStack.length - 1];
|
||||
const tooltip = undoAction ? `Undo '${undoAction.description}'` : 'Undo';
|
||||
const tooltip = `Undo${undoAction?.description ? ` '${undoAction.description}'` : ''}`;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
id={undoButtonID}
|
||||
icon="corner-up-left"
|
||||
disabled={undoStack.length === 0}
|
||||
onClick={() => editPane.undoAction()}
|
||||
@@ -181,14 +183,16 @@ function UndoButton({ dashboard }: ToolbarActionProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export const redoButtonId = 'redo-button';
|
||||
function RedoButton({ dashboard }: ToolbarActionProps) {
|
||||
const editPane = dashboard.state.editPane;
|
||||
const { redoStack } = editPane.useState();
|
||||
const redoAction = redoStack[redoStack.length - 1];
|
||||
const tooltip = redoAction ? `Redo '${redoAction?.description}'` : 'Redo';
|
||||
const tooltip = `Redo${redoAction?.description ? ` '${redoAction.description}'` : ''}`;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
id={redoButtonId}
|
||||
icon="corner-up-right"
|
||||
disabled={redoStack.length === 0}
|
||||
tooltip={tooltip}
|
||||
|
||||
@@ -4606,6 +4606,9 @@
|
||||
"title-not-unique": "This title is not unique"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"action": "Change dashboard title"
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Add",
|
||||
"alert-rules": "Alert rules",
|
||||
|
||||
Reference in New Issue
Block a user