diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx index e9c5c31e85b..cef94e5be88 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx @@ -263,7 +263,7 @@ function getStyles(theme: GrafanaTheme2) { }), tabsbar: css({ padding: theme.spacing(0, 1), - margin: theme.spacing(0.5, 1), + margin: theme.spacing(0.5, 0), }), expandOptionsWrapper: css({ display: 'flex', diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx index e324b09075c..7ccf14b4ff6 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx @@ -15,7 +15,11 @@ export class DashboardEditableElement implements EditableDashboardElement { public constructor(private dashboard: DashboardScene) {} public getEditableElementInfo(): EditableDashboardElementInfo { - return { typeId: 'dashboard', icon: 'apps', name: t('dashboard.edit-pane.elements.dashboard', 'Dashboard') }; + return { + typeName: t('dashboard.edit-pane.elements.dashboard', 'Dashboard'), + icon: 'apps', + instanceName: this.dashboard.state.title, + }; } public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] { @@ -25,11 +29,7 @@ export class DashboardEditableElement implements EditableDashboardElement { const { body } = dashboard.useState(); const dashboardOptions = useMemo(() => { - const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({ - title: t('dashboard.options.title', 'Dashboard options'), - id: 'dashboard-options', - isOpenable: false, - }) + const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({ title: '', id: 'dashboard-options' }) .addItem( new OptionsPaneItemDescriptor({ title: t('dashboard.options.title-option', 'Title'), diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx index 4f302a638fd..be3066396f4 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx @@ -65,7 +65,7 @@ function DashboardOutlineNode({ sceneObject, expandable }: { sceneObject: SceneO onPointerDown={(evt) => onSelect?.(evt)} > - {elementInfo.name} + {elementInfo.instanceName} {expandable && isExpanded && ( diff --git a/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx index 653daf384f2..5f9828c834b 100644 --- a/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx +++ b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx @@ -1,57 +1,86 @@ -import { Dropdown, Button, IconButton, Menu, Stack, Icon } from '@grafana/ui'; +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, Menu, Stack, Text, useStyles2, ConfirmButton, Dropdown, Icon } from '@grafana/ui'; import { t } from 'app/core/internationalization'; +import { EditableDashboardElement } from '../scene/types/EditableDashboardElement'; + interface EditPaneHeaderProps { - title: string; - onDelete?: () => void; - onCopy?: () => void; - onDuplicate?: () => void; + element: EditableDashboardElement; } -export const EditPaneHeader = ({ title, onDelete, onCopy, onDuplicate }: EditPaneHeaderProps) => { - const addCopyOrDuplicate = onCopy || onDuplicate; +export function EditPaneHeader({ element }: EditPaneHeaderProps) { + const elementInfo = element.getEditableElementInfo(); + const styles = useStyles2(getStyles); + + const onCopy = element.onCopy?.bind(element); + const onDuplicate = element.onDuplicate?.bind(element); + const onDelete = element.onDelete?.bind(element); + return ( - - {title} - - {addCopyOrDuplicate ? ( - }> +
+ {elementInfo.typeName} + + {(onCopy || onDelete) && ( + + {onCopy ? ( + + ) : null} + {onDuplicate ? ( + + ) : null} + + } + > - ) : null} + )} - + {onDelete && ( + +
); -}; +} -type MenuItemsProps = { - onCopy?: () => void; - onDuplicate?: () => void; -}; - -const MenuItems = ({ onCopy, onDuplicate }: MenuItemsProps) => { - return ( - - {onCopy ? : null} - {onDuplicate ? ( - - ) : null} - - ); -}; +function getStyles(theme: GrafanaTheme2) { + return { + wrapper: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(2), + borderBottom: `1px solid ${theme.colors.border.weak}`, + }), + }; +} diff --git a/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx index 01b9a3f2398..f60b4bd7e26 100644 --- a/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx +++ b/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx @@ -1,50 +1,20 @@ -import { css } from '@emotion/css'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Stack, useStyles2 } from '@grafana/ui'; -import { OptionsPaneCategory } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategory'; +import { Stack } from '@grafana/ui'; import { EditableDashboardElement } from '../scene/types/EditableDashboardElement'; -import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement'; + +import { EditPaneHeader } from './EditPaneHeader'; export interface Props { - element: EditableDashboardElement | MultiSelectedEditableDashboardElement; + element: EditableDashboardElement; } export function ElementEditPane({ element }: Props) { const categories = element.useEditPaneOptions ? element.useEditPaneOptions() : []; - const styles = useStyles2(getStyles); - const elementInfo = element.getEditableElementInfo(); return ( - {element.renderActions && ( - -
{element.renderActions()}
-
- )} + {categories.map((cat) => cat.render())}
); } - -function getStyles(theme: GrafanaTheme2) { - return { - noBorderTop: css({ - borderTop: 'none', - }), - actionsBox: css({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), - paddingBottom: theme.spacing(1), - }), - }; -} diff --git a/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts b/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts index 2141781408d..0bae42a69f4 100644 --- a/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts +++ b/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts @@ -1,17 +1,14 @@ -import { SceneObject, SceneObjectRef, VizPanel } from '@grafana/scenes'; +import { SceneObject, SceneObjectRef } from '@grafana/scenes'; import { ElementSelectionContextItem } from '@grafana/ui'; import { isBulkActionElement } from '../scene/types/BulkActionElement'; -import { EditableDashboardElement, isEditableDashboardElement } from '../scene/types/EditableDashboardElement'; -import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement'; +import { EditableDashboardElement } from '../scene/types/EditableDashboardElement'; import { MultiSelectedObjectsEditableElement } from './MultiSelectedObjectsEditableElement'; -import { MultiSelectedVizPanelsEditableElement } from './MultiSelectedVizPanelsEditableElement'; -import { VizPanelEditableElement } from './VizPanelEditableElement'; import { getEditableElementFor } from './shared'; export class ElementSelection { - private selectedObjects?: Map>; + private selectedObjects: Map>; private sameType?: boolean; private _isMultiSelection: boolean; @@ -26,15 +23,15 @@ export class ElementSelection { } private checkSameType() { - const values = this.selectedObjects?.values(); - const firstType = values?.next().value?.resolve()?.constructor.name; + const values = this.selectedObjects.values(); + const firstType = values.next().value?.resolve().constructor.name; if (!firstType) { return false; } for (let obj of values ?? []) { - if (obj.resolve()?.constructor.name !== firstType) { + if (obj.resolve().constructor.name !== firstType) { return false; } } @@ -43,13 +40,13 @@ export class ElementSelection { } public hasValue(id: string) { - return this.selectedObjects?.has(id); + return this.selectedObjects.has(id); } public removeValue(id: string) { - this.selectedObjects?.delete(id); + this.selectedObjects.delete(id); - if (this.selectedObjects && this.selectedObjects.size < 2) { + if (this.selectedObjects.size < 2) { this.sameType = undefined; this._isMultiSelection = false; } @@ -95,11 +92,11 @@ export class ElementSelection { } public getSelectionEntries(): Array<[string, SceneObjectRef]> { - return Array.from(this.selectedObjects?.entries() ?? []); + return Array.from(this.selectedObjects.entries()); } public getFirstObject(): SceneObject | undefined { - return this.selectedObjects?.values().next().value?.resolve(); + return this.selectedObjects.values().next().value?.resolve(); } public get isMultiSelection(): boolean { @@ -107,51 +104,38 @@ export class ElementSelection { } private getSceneObjects(): SceneObject[] { - return Array.from(this.selectedObjects?.values() ?? []).map((obj) => obj.resolve()); + return Array.from(this.selectedObjects.values() ?? []).map((obj) => obj.resolve()); } - public createSelectionElement() { - if (this.isMultiSelection) { - return this.createMultiSelectedElement(); - } - - return this.createSingleSelectedElement(); - } - - private createSingleSelectedElement(): EditableDashboardElement | undefined { - const sceneObj = this.selectedObjects?.values().next().value?.resolve(); - return getEditableElementFor(sceneObj); - } - - private createMultiSelectedElement(): MultiSelectedEditableDashboardElement | undefined { - if (!this.isMultiSelection) { - return; - } - + public createSelectionElement(): EditableDashboardElement | undefined { const sceneObjects = this.getSceneObjects(); - if (this.sameType) { - const firstObj = this.selectedObjects?.values().next().value?.resolve(); + if (sceneObjects.length === 0) { + return undefined; + } - if (firstObj instanceof VizPanel) { - return new MultiSelectedVizPanelsEditableElement(sceneObjects.filter((obj) => obj instanceof VizPanel)); - } + const firstElement = getEditableElementFor(sceneObjects[0]); - if (isEditableDashboardElement(firstObj!)) { - return firstObj.createMultiSelectedElement?.(sceneObjects); - } + if (!firstElement) { + return undefined; + } + + if (sceneObjects.length === 1) { + return firstElement; + } + + if (this.sameType && firstElement.createMultiSelectedElement) { + const elements = sceneObjects.map((obj) => getEditableElementFor(obj)!); + return firstElement.createMultiSelectedElement(elements); } const bulkActionElements = []; for (const sceneObject of sceneObjects) { - if (sceneObject instanceof VizPanel) { - const editableElement = new VizPanelEditableElement(sceneObject); - bulkActionElements.push(editableElement); - } + const element = getEditableElementFor(sceneObject); - if (isBulkActionElement(sceneObject)) { - bulkActionElements.push(sceneObject); + if (element && isBulkActionElement(element)) { + bulkActionElements.push(element); } } diff --git a/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx index a55b5f0d8c9..25bcdd807c9 100644 --- a/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx +++ b/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx @@ -1,42 +1,20 @@ -import { ReactNode } from 'react'; -import { v4 as uuidv4 } from 'uuid'; - -import { Stack, Text, Button } from '@grafana/ui'; -import { t, Trans } from 'app/core/internationalization'; +import { t } from 'app/core/internationalization'; +import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { BulkActionElement } from '../scene/types/BulkActionElement'; -import { EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement'; -import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement'; +import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement'; -export class MultiSelectedObjectsEditableElement implements MultiSelectedEditableDashboardElement { - public readonly isMultiSelectedEditableDashboardElement = true; - public readonly key: string; +export class MultiSelectedObjectsEditableElement implements EditableDashboardElement { + public readonly isEditableDashboardElement = true; - constructor(private _elements: BulkActionElement[]) { - this.key = uuidv4(); + constructor(private _elements: BulkActionElement[]) {} + + public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] { + return []; } public getEditableElementInfo(): EditableDashboardElementInfo { - return { name: t('dashboard.edit-pane.elements.objects', 'Objects'), typeId: 'objects', icon: 'folder' }; - } - - public renderActions(): ReactNode { - return ( - - - - No. of objects selected: {{ length }} - - - -