Dashboard: Edit pane header design and refactorings (#101851)
* Edit pane header * Update * Update * Progress * Progress * Update * Delete * Delete icon button * Update * Fix * remove need for arrow functions * Update * update * Update * Update * Update
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -65,7 +65,7 @@ function DashboardOutlineNode({ sceneObject, expandable }: { sceneObject: SceneO
|
||||
onPointerDown={(evt) => onSelect?.(evt)}
|
||||
>
|
||||
<Icon name={elementInfo.icon} />
|
||||
<span>{elementInfo.name}</span>
|
||||
<span>{elementInfo.instanceName}</span>
|
||||
</button>
|
||||
</Stack>
|
||||
{expandable && isExpanded && (
|
||||
|
||||
@@ -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 (
|
||||
<Stack justifyContent="space-between" alignItems="center" width="100%">
|
||||
<span>{title}</span>
|
||||
<Stack alignItems="center">
|
||||
{addCopyOrDuplicate ? (
|
||||
<Dropdown overlay={<MenuItems onCopy={onCopy} onDuplicate={onDuplicate} />}>
|
||||
<div className={styles.wrapper}>
|
||||
<Text variant="h5">{elementInfo.typeName}</Text>
|
||||
<Stack direction="row" gap={1}>
|
||||
{(onCopy || onDelete) && (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
{onCopy ? (
|
||||
<Menu.Item icon="copy" label={t('dashboard.layout.common.copy', 'Copy')} onClick={onCopy} />
|
||||
) : null}
|
||||
{onDuplicate ? (
|
||||
<Menu.Item
|
||||
icon="file-copy-alt"
|
||||
label={t('dashboard.layout.common.duplicate', 'Duplicate')}
|
||||
onClick={onDuplicate}
|
||||
/>
|
||||
) : null}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
tooltip={t('dashboard.layout.common.copy-or-duplicate', 'Copy or Duplicate')}
|
||||
tooltipPlacement="bottom"
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
size="md"
|
||||
size="sm"
|
||||
icon="copy"
|
||||
>
|
||||
<Icon name="copy" /> <Icon name="angle-down" />
|
||||
<Icon name="angle-down" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
size="md"
|
||||
variant="secondary"
|
||||
onClick={onDelete}
|
||||
name="trash-alt"
|
||||
tooltip={t('dashboard.layout.common.delete', 'Delete')}
|
||||
/>
|
||||
{onDelete && (
|
||||
<ConfirmButton
|
||||
onConfirm={onDelete}
|
||||
confirmText="Confirm"
|
||||
confirmVariant="destructive"
|
||||
size="sm"
|
||||
closeOnConfirm={true}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
fill="outline"
|
||||
icon="trash-alt"
|
||||
tooltip={t('dashboard.layout.common.delete', 'Delete')}
|
||||
/>
|
||||
</ConfirmButton>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
type MenuItemsProps = {
|
||||
onCopy?: () => void;
|
||||
onDuplicate?: () => void;
|
||||
};
|
||||
|
||||
const MenuItems = ({ onCopy, onDuplicate }: MenuItemsProps) => {
|
||||
return (
|
||||
<Menu>
|
||||
{onCopy ? <Menu.Item label={t('dashboard.layout.common.copy', 'Copy')} onClick={onCopy} /> : null}
|
||||
{onDuplicate ? (
|
||||
<Menu.Item label={t('dashboard.layout.common.duplicate', 'Duplicate')} onClick={onDuplicate} />
|
||||
) : null}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
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}`,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<Stack direction="column" gap={0}>
|
||||
{element.renderActions && (
|
||||
<OptionsPaneCategory
|
||||
id="selected-item"
|
||||
title={elementInfo.name}
|
||||
isOpenDefault={true}
|
||||
className={styles.noBorderTop}
|
||||
renderTitle={element.renderTitle}
|
||||
isOpenable={element.isOpenable}
|
||||
>
|
||||
<div className={styles.actionsBox}>{element.renderActions()}</div>
|
||||
</OptionsPaneCategory>
|
||||
)}
|
||||
<EditPaneHeader element={element} />
|
||||
{categories.map((cat) => cat.render())}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
noBorderTop: css({
|
||||
borderTop: 'none',
|
||||
}),
|
||||
actionsBox: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
paddingBottom: theme.spacing(1),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<string, SceneObjectRef<SceneObject>>;
|
||||
private selectedObjects: Map<string, SceneObjectRef<SceneObject>>;
|
||||
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<SceneObject>]> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-32
@@ -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 (
|
||||
<Stack direction="column">
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="dashboard.edit-pane.objects.multi-select.selection-number"
|
||||
values={{ length: this._elements.length }}
|
||||
>
|
||||
No. of objects selected: {{ length }}
|
||||
</Trans>
|
||||
</Text>
|
||||
<Stack direction="row">
|
||||
<Button size="sm" variant="secondary" icon="copy" />
|
||||
<Button size="sm" variant="destructive" fill="outline" onClick={() => this.onDelete()} icon="trash-alt" />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
return { typeName: t('dashboard.edit-pane.elements.objects', 'Objects'), icon: 'folder', instanceName: '' };
|
||||
}
|
||||
|
||||
public onDelete() {
|
||||
|
||||
+8
-21
@@ -1,40 +1,28 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||
|
||||
import { EditPaneHeader } from './EditPaneHeader';
|
||||
import { VizPanelEditableElement } from './VizPanelEditableElement';
|
||||
|
||||
export class MultiSelectedVizPanelsEditableElement implements MultiSelectedEditableDashboardElement {
|
||||
public readonly isMultiSelectedEditableDashboardElement = true;
|
||||
export class MultiSelectedVizPanelsEditableElement implements EditableDashboardElement {
|
||||
public readonly isEditableDashboardElement = true;
|
||||
public readonly key: string;
|
||||
|
||||
constructor(private _panels: VizPanel[]) {
|
||||
constructor(private _panels: VizPanelEditableElement[]) {
|
||||
this.key = uuidv4();
|
||||
}
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return { name: t('dashboard.edit-pane.elements.panels', 'Panels'), typeId: 'panels', icon: 'folder' };
|
||||
return { typeName: t('dashboard.edit-pane.elements.panels', 'Panels'), icon: 'folder', instanceName: '' };
|
||||
}
|
||||
|
||||
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
|
||||
const header = new OptionsPaneCategoryDescriptor({
|
||||
title: ``,
|
||||
id: 'panel-header',
|
||||
isOpenable: false,
|
||||
renderTitle: () => (
|
||||
<EditPaneHeader
|
||||
title={t('dashboard.layout.common.panels-title', '{{length}} panels selected', {
|
||||
length: this._panels.length,
|
||||
})}
|
||||
onDelete={() => this.onDelete()}
|
||||
/>
|
||||
),
|
||||
id: '',
|
||||
});
|
||||
|
||||
return [header];
|
||||
@@ -42,8 +30,7 @@ export class MultiSelectedVizPanelsEditableElement implements MultiSelectedEdita
|
||||
|
||||
public onDelete() {
|
||||
this._panels.forEach((panel) => {
|
||||
const layout = dashboardSceneGraph.getLayoutManagerFor(panel);
|
||||
layout.removePanel?.(panel);
|
||||
panel.onDelete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ import { isDashboardLayoutItem } from '../scene/types/DashboardLayoutItem';
|
||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { getEditPanelUrl } from '../utils/urlBuilders';
|
||||
import { getPanelIdForVizPanel } from '../utils/utils';
|
||||
import { getDashboardSceneFor, getPanelIdForVizPanel } from '../utils/utils';
|
||||
|
||||
import { EditPaneHeader } from './EditPaneHeader';
|
||||
import { MultiSelectedVizPanelsEditableElement } from './MultiSelectedVizPanelsEditableElement';
|
||||
|
||||
export class VizPanelEditableElement implements EditableDashboardElement, BulkActionElement {
|
||||
public readonly isEditableDashboardElement = true;
|
||||
@@ -30,9 +30,9 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return {
|
||||
typeId: 'panel',
|
||||
typeName: t('dashboard.edit-pane.elements.panel', 'Panel'),
|
||||
icon: 'chart-line',
|
||||
name: sceneGraph.interpolate(this.panel, this.panel.state.title, undefined, 'text'),
|
||||
instanceName: sceneGraph.interpolate(this.panel, this.panel.state.title, undefined, 'text'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,14 +41,7 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
|
||||
const layoutElement = panel.parent!;
|
||||
|
||||
const panelOptions = useMemo(() => {
|
||||
return new OptionsPaneCategoryDescriptor({
|
||||
title: ``,
|
||||
id: 'panel-header',
|
||||
isOpenable: false,
|
||||
renderTitle: () => (
|
||||
<EditPaneHeader title={t('dashboard.viz-panel.options.title', 'Panel')} onDelete={() => this.onDelete()} />
|
||||
),
|
||||
})
|
||||
return new OptionsPaneCategoryDescriptor({ title: '', id: 'panel-options' })
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: '',
|
||||
@@ -97,6 +90,20 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
|
||||
const layout = dashboardSceneGraph.getLayoutManagerFor(this.panel);
|
||||
layout.removePanel?.(this.panel);
|
||||
}
|
||||
|
||||
public onDuplicate() {
|
||||
const layout = dashboardSceneGraph.getLayoutManagerFor(this.panel);
|
||||
layout.duplicatePanel?.(this.panel);
|
||||
}
|
||||
|
||||
public onCopy() {
|
||||
const dashboard = getDashboardSceneFor(this.panel);
|
||||
dashboard.copyPanel(this.panel);
|
||||
}
|
||||
|
||||
public createMultiSelectedElement(items: VizPanelEditableElement[]) {
|
||||
return new MultiSelectedVizPanelsEditableElement(items);
|
||||
}
|
||||
}
|
||||
|
||||
type OpenPanelEditVizProps = {
|
||||
@@ -112,7 +119,7 @@ const OpenPanelEditViz = ({ panel }: OpenPanelEditVizProps) => {
|
||||
return (
|
||||
<Stack alignItems="center" width="100%">
|
||||
{plugin ? (
|
||||
<Tooltip content={t('dashboard.viz-panel.options.open-edit', 'Open Panel Edit')}>
|
||||
<Tooltip content={t('dashboard.viz-panel.options.open-edit', 'Open panel editor')}>
|
||||
<a
|
||||
href={textUtil.sanitizeUrl(getEditPanelUrl(getPanelIdForVizPanel(panel)))}
|
||||
className={cx(styles.pluginDescriptionWrapper)}
|
||||
@@ -139,12 +146,15 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
columnGap: theme.spacing(1),
|
||||
rowGap: theme.spacing(0.5),
|
||||
minHeight: theme.spacing(4),
|
||||
backgroundColor: theme.components.input.background,
|
||||
border: `1px solid ${theme.colors.border.strong}`,
|
||||
backgroundColor: theme.colors.secondary.main,
|
||||
border: `1px solid ${theme.colors.secondary.border}`,
|
||||
borderRadius: theme.shape.radius.default,
|
||||
paddingInline: theme.spacing(1),
|
||||
paddingBlock: theme.spacing(0.5),
|
||||
flexGrow: 1,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.colors.secondary.shade,
|
||||
},
|
||||
}),
|
||||
panelVizImg: css({
|
||||
width: '16px',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { EditableDashboardElement } from '../scene/types/EditableDashboardElement';
|
||||
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { DashboardEditPane } from './DashboardEditPane';
|
||||
@@ -10,7 +9,7 @@ import { ElementSelection } from './ElementSelection';
|
||||
export function useEditableElement(
|
||||
selection: ElementSelection | undefined,
|
||||
editPane: DashboardEditPane
|
||||
): EditableDashboardElement | MultiSelectedEditableDashboardElement | undefined {
|
||||
): EditableDashboardElement | undefined {
|
||||
return useMemo(() => {
|
||||
if (!selection) {
|
||||
const dashboard = getDashboardSceneFor(editPane);
|
||||
|
||||
+4
-12
@@ -1,8 +1,8 @@
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { sceneGraph, SceneGridRow, VizPanel } from '@grafana/scenes';
|
||||
import { Alert, Button, Input, TextLink } from '@grafana/ui';
|
||||
import { Alert, Input, TextLink } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
@@ -25,9 +25,9 @@ export class SceneGridRowEditableElement implements EditableDashboardElement, Bu
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return {
|
||||
typeId: 'panel',
|
||||
typeName: t('dashboard.edit-pane.elements.row', 'Row'),
|
||||
instanceName: sceneGraph.interpolate(this._row, this._row.state.title, undefined, 'text'),
|
||||
icon: 'line-alt',
|
||||
name: sceneGraph.interpolate(this._row, this._row.state.title, undefined, 'text'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,14 +72,6 @@ export class SceneGridRowEditableElement implements EditableDashboardElement, Bu
|
||||
layoutManager.removeRow(this._row);
|
||||
}
|
||||
}
|
||||
|
||||
public renderActions(): ReactNode {
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" variant="destructive" fill="outline" onClick={() => this.onDelete()} icon="trash-alt" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function RowTitleInput({ row }: { row: SceneGridRow }) {
|
||||
|
||||
@@ -44,7 +44,11 @@ export class RowItem
|
||||
}
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return { typeId: 'row', icon: 'line-alt', name: sceneGraph.interpolate(this, this.state.title, undefined, 'text') };
|
||||
return {
|
||||
typeName: t('dashboard.edit-pane.elements.row', 'Row'),
|
||||
instanceName: sceneGraph.interpolate(this, this.state.title, undefined, 'text'),
|
||||
icon: 'line-alt',
|
||||
};
|
||||
}
|
||||
|
||||
public getLayout(): DashboardLayoutManager {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSel
|
||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/constants';
|
||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||
|
||||
import { EditPaneHeader } from '../../edit-pane/EditPaneHeader';
|
||||
import { getDashboardSceneFor, getQueryRunnerFor } from '../../utils/utils';
|
||||
import { DashboardScene } from '../DashboardScene';
|
||||
import { DashboardLayoutSelector } from '../layouts-shared/DashboardLayoutSelector';
|
||||
@@ -23,14 +22,7 @@ export function getEditOptions(model: RowItem): OptionsPaneCategoryDescriptor[]
|
||||
const rowOptions = useMemo(() => {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
|
||||
const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({
|
||||
title: t('dashboard.rows-layout.item-name', 'Row'),
|
||||
id: 'row-options',
|
||||
isOpenable: false,
|
||||
renderTitle: () => (
|
||||
<EditPaneHeader title={t('dashboard.rows-layout.item-name', 'Row')} onDelete={() => model.onDelete()} />
|
||||
),
|
||||
})
|
||||
const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({ title: '', id: 'row-options' })
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.rows-layout.option.title', 'Title'),
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { EditableDashboardElementInfo } from '../types/EditableDashboardElement';
|
||||
import { MultiSelectedEditableDashboardElement } from '../types/MultiSelectedEditableDashboardElement';
|
||||
import { EditableDashboardElementInfo, EditableDashboardElement } from '../types/EditableDashboardElement';
|
||||
|
||||
import { RowItem } from './RowItem';
|
||||
import { getEditOptions } from './RowItemsEditor';
|
||||
|
||||
export class RowItems implements MultiSelectedEditableDashboardElement {
|
||||
public readonly isMultiSelectedEditableDashboardElement = true;
|
||||
public readonly key: string;
|
||||
export class RowItems implements EditableDashboardElement {
|
||||
public readonly isEditableDashboardElement = true;
|
||||
|
||||
public constructor(private _rows: RowItem[]) {
|
||||
this.key = uuidv4();
|
||||
}
|
||||
public constructor(private _rows: RowItem[]) {}
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return { name: t('dashboard.edit-pane.elements.rows', 'Rows'), typeId: 'rows', icon: 'folder' };
|
||||
return { typeName: t('dashboard.edit-pane.elements.rows', 'Rows'), icon: 'folder', instanceName: '' };
|
||||
}
|
||||
|
||||
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
|
||||
@@ -36,6 +30,4 @@ export class RowItems implements MultiSelectedEditableDashboardElement {
|
||||
public onHeaderHiddenToggle(value: boolean, indeterminate: boolean) {
|
||||
this._rows.forEach((row) => row.onHeaderHiddenToggle(indeterminate ? true : !value));
|
||||
}
|
||||
|
||||
public getNumberOfRowsSelected = () => this._rows.length;
|
||||
}
|
||||
|
||||
@@ -3,24 +3,10 @@ import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
import { EditPaneHeader } from '../../edit-pane/EditPaneHeader';
|
||||
|
||||
import { RowItems } from './RowItems';
|
||||
|
||||
export function getEditOptions(model: RowItems): OptionsPaneCategoryDescriptor[] {
|
||||
const options = new OptionsPaneCategoryDescriptor({
|
||||
title: '',
|
||||
id: `ms-row-options-${model.key}`,
|
||||
isOpenable: false,
|
||||
renderTitle: () => (
|
||||
<EditPaneHeader
|
||||
title={t('dashboard.edit-pane.row.multi-select.title', '{{length}} rows selected', {
|
||||
length: model.getNumberOfRowsSelected(),
|
||||
})}
|
||||
onDelete={() => model.onDelete()}
|
||||
/>
|
||||
),
|
||||
}).addItem(
|
||||
const options = new OptionsPaneCategoryDescriptor({ title: '', id: `rows-options` }).addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.edit-pane.row.header.title', 'Row header'),
|
||||
render: () => <RowHeaderCheckboxMulti model={model} />,
|
||||
|
||||
@@ -40,7 +40,11 @@ export class TabItem
|
||||
}
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return { typeId: 'tab', icon: 'tag-alt', name: sceneGraph.interpolate(this, this.state.title, undefined, 'text') };
|
||||
return {
|
||||
typeName: t('dashboard.edit-pane.elements.tab', 'Tab'),
|
||||
instanceName: sceneGraph.interpolate(this, this.state.title, undefined, 'text'),
|
||||
icon: 'tag-alt',
|
||||
};
|
||||
}
|
||||
|
||||
public getLayout(): DashboardLayoutManager {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
import { EditPaneHeader } from '../../edit-pane/EditPaneHeader';
|
||||
import { useLayoutCategory } from '../layouts-shared/DashboardLayoutSelector';
|
||||
import { useEditPaneInputAutoFocus } from '../layouts-shared/utils';
|
||||
|
||||
@@ -13,14 +12,7 @@ import { TabItem } from './TabItem';
|
||||
|
||||
export function getEditOptions(model: TabItem): OptionsPaneCategoryDescriptor[] {
|
||||
const tabOptions = useMemo(() => {
|
||||
return new OptionsPaneCategoryDescriptor({
|
||||
title: '',
|
||||
id: 'tab-options',
|
||||
isOpenable: false,
|
||||
renderTitle: () => (
|
||||
<EditPaneHeader title={t('dashboard.tabs-layout.tab-options.title', 'Tab')} onDelete={() => model.onDelete()} />
|
||||
),
|
||||
}).addItem(
|
||||
return new OptionsPaneCategoryDescriptor({ title: '', id: 'tab-item-options' }).addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.tabs-layout.tab-options.title-option', 'Title'),
|
||||
render: () => <TabTitleInput tab={model} />,
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { EditableDashboardElementInfo } from '../types/EditableDashboardElement';
|
||||
import { MultiSelectedEditableDashboardElement } from '../types/MultiSelectedEditableDashboardElement';
|
||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement';
|
||||
|
||||
import { TabItem } from './TabItem';
|
||||
import { getEditOptions } from './TabItemsEditor';
|
||||
|
||||
export class TabItems implements MultiSelectedEditableDashboardElement {
|
||||
public readonly isMultiSelectedEditableDashboardElement = true;
|
||||
public readonly key: string;
|
||||
export class TabItems implements EditableDashboardElement {
|
||||
public readonly isEditableDashboardElement = true;
|
||||
|
||||
public constructor(private _tabs: TabItem[]) {
|
||||
this.key = uuidv4();
|
||||
}
|
||||
public constructor(private _tabs: TabItem[]) {}
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return { name: t('dashboard.edit-pane.elements.tabs', 'Tabs'), typeId: 'tabs', icon: 'folder' };
|
||||
return { typeName: t('dashboard.edit-pane.elements.tabs', 'Tabs'), icon: 'folder', instanceName: '' };
|
||||
}
|
||||
|
||||
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
|
||||
return getEditOptions(this);
|
||||
return [];
|
||||
}
|
||||
|
||||
public getTabs(): TabItem[] {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { EditPaneHeader } from '../../edit-pane/EditPaneHeader';
|
||||
|
||||
import { TabItems } from './TabItems';
|
||||
|
||||
export function getEditOptions(model: TabItems): OptionsPaneCategoryDescriptor[] {
|
||||
const tabOptions = useMemo(() => {
|
||||
const tabs = model.getTabs();
|
||||
return new OptionsPaneCategoryDescriptor({
|
||||
title: ``,
|
||||
id: 'ms-tab-options',
|
||||
isOpenable: false,
|
||||
renderTitle: () => (
|
||||
<EditPaneHeader
|
||||
title={t('dashboard.tabs-layout.multi-select.title', '{{length}} tabs selected', { length: tabs.length })}
|
||||
onDelete={() => model.onDelete()}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [model]);
|
||||
|
||||
return [tabOptions];
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { IconName } from '@grafana/data';
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { MultiSelectedEditableDashboardElement } from './MultiSelectedEditableDashboardElement';
|
||||
|
||||
/**
|
||||
* Interface for elements that have options
|
||||
*/
|
||||
@@ -24,29 +19,29 @@ export interface EditableDashboardElement {
|
||||
useEditPaneOptions(): OptionsPaneCategoryDescriptor[];
|
||||
|
||||
/**
|
||||
* Panel Actions
|
||||
**/
|
||||
renderActions?(): ReactNode;
|
||||
* Supports delete action
|
||||
*/
|
||||
onDelete?(): void;
|
||||
|
||||
/**
|
||||
* Supports duplicate action
|
||||
*/
|
||||
onDuplicate?(): void;
|
||||
|
||||
/**
|
||||
* Supports copy action
|
||||
*/
|
||||
onCopy?(): void;
|
||||
|
||||
/**
|
||||
* creates a new multi-selection element from a list of selected items
|
||||
*/
|
||||
createMultiSelectedElement?(items: SceneObject[]): MultiSelectedEditableDashboardElement;
|
||||
|
||||
/**
|
||||
* Return custom title for the edit panel header
|
||||
*/
|
||||
renderTitle?(): ReactNode;
|
||||
|
||||
/**
|
||||
* determines if first edit panel header can be collapsed
|
||||
*/
|
||||
isOpenable?: Readonly<boolean>;
|
||||
createMultiSelectedElement?(elements: this[]): EditableDashboardElement;
|
||||
}
|
||||
|
||||
export interface EditableDashboardElementInfo {
|
||||
name: string;
|
||||
typeId: string;
|
||||
instanceName: string;
|
||||
typeName: string;
|
||||
icon: IconName;
|
||||
}
|
||||
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { EditableDashboardElementInfo } from './EditableDashboardElement';
|
||||
|
||||
export interface MultiSelectedEditableDashboardElement {
|
||||
/**
|
||||
* Marks this object as an element that can be selected and edited directly on the canvas
|
||||
*/
|
||||
isMultiSelectedEditableDashboardElement: true;
|
||||
|
||||
/** A descriptor used by editing pane */
|
||||
getEditableElementInfo(): EditableDashboardElementInfo;
|
||||
|
||||
/**
|
||||
* Extremely useful for being able to access the useState inside the contained items
|
||||
*/
|
||||
key: Readonly<string>;
|
||||
|
||||
/**
|
||||
* Hook that returns edit pane options
|
||||
*/
|
||||
useEditPaneOptions?(): OptionsPaneCategoryDescriptor[];
|
||||
|
||||
/**
|
||||
* Panel Actions
|
||||
**/
|
||||
renderActions?(): ReactNode;
|
||||
|
||||
/**
|
||||
* Return custom title for the edit panel header
|
||||
*/
|
||||
renderTitle?(): ReactNode;
|
||||
|
||||
/**
|
||||
* determines if first edit panel header can be collapsed
|
||||
*/
|
||||
isOpenable?: Readonly<boolean>;
|
||||
}
|
||||
|
||||
export function isMultiSelectedEditableDashboardElement(obj: object): obj is MultiSelectedEditableDashboardElement {
|
||||
return 'isMultiSelectedEditableDashboardElement' in obj;
|
||||
}
|
||||
@@ -21,7 +21,6 @@ export interface OptionsPaneCategoryProps {
|
||||
isNested?: boolean;
|
||||
children: ReactNode;
|
||||
sandboxId?: string;
|
||||
isOpenable?: boolean;
|
||||
}
|
||||
|
||||
const CATEGORY_PARAM_NAME = 'showCategory' as const;
|
||||
@@ -38,13 +37,12 @@ export const OptionsPaneCategory = React.memo(
|
||||
itemsCount,
|
||||
isNested = false,
|
||||
sandboxId,
|
||||
isOpenable = true,
|
||||
}: OptionsPaneCategoryProps) => {
|
||||
const [savedState, setSavedState] = useLocalStorage(getOptionGroupStorageKey(id), {
|
||||
isExpanded: isOpenDefault,
|
||||
});
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(!isOpenable || (savedState?.isExpanded ?? isOpenDefault));
|
||||
const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? isOpenDefault);
|
||||
const manualClickTime = useRef(0);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [queryParams, updateQueryParams] = useQueryParams();
|
||||
@@ -68,9 +66,6 @@ export const OptionsPaneCategory = React.memo(
|
||||
}, [forceOpen, isExpanded, isOpenFromUrl]);
|
||||
|
||||
const onToggle = useCallback(() => {
|
||||
if (!isOpenable) {
|
||||
return;
|
||||
}
|
||||
manualClickTime.current = Date.now();
|
||||
updateQueryParams(
|
||||
{
|
||||
@@ -80,7 +75,7 @@ export const OptionsPaneCategory = React.memo(
|
||||
);
|
||||
setSavedState({ isExpanded: !isExpanded });
|
||||
setIsExpanded(!isExpanded);
|
||||
}, [isOpenable, updateQueryParams, isExpanded, id, setSavedState]);
|
||||
}, [updateQueryParams, isExpanded, id, setSavedState]);
|
||||
|
||||
if (!renderTitle) {
|
||||
renderTitle = function defaultTitle(isExpanded: boolean) {
|
||||
@@ -106,7 +101,6 @@ export const OptionsPaneCategory = React.memo(
|
||||
);
|
||||
|
||||
const headerStyles = cx(styles.header, {
|
||||
[styles.headerHover]: isOpenable,
|
||||
[styles.headerExpanded]: isExpanded,
|
||||
[styles.headerNested]: isNested,
|
||||
});
|
||||
@@ -129,19 +123,18 @@ export const OptionsPaneCategory = React.memo(
|
||||
<h6 id={`button-${id}`} className={styles.title}>
|
||||
{renderTitle(isExpanded)}
|
||||
</h6>
|
||||
{isOpenable ? (
|
||||
<Button
|
||||
data-testid={selectors.components.OptionsGroup.toggle(id)}
|
||||
type="button"
|
||||
fill="text"
|
||||
size="md"
|
||||
variant="secondary"
|
||||
aria-expanded={isExpanded}
|
||||
className={styles.toggleButton}
|
||||
icon={isExpanded ? 'angle-up' : 'angle-down'}
|
||||
onClick={onToggle}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
data-testid={selectors.components.OptionsGroup.toggle(id)}
|
||||
type="button"
|
||||
fill="text"
|
||||
size="md"
|
||||
variant="secondary"
|
||||
aria-expanded={isExpanded}
|
||||
className={styles.toggleButton}
|
||||
icon={isExpanded ? 'angle-up' : 'angle-down'}
|
||||
onClick={onToggle}
|
||||
/>
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<div className={bodyStyles} id={id} aria-labelledby={`button-${id}`}>
|
||||
@@ -179,8 +172,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
padding: theme.spacing(0.5, 1.5),
|
||||
color: theme.colors.text.primary,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
}),
|
||||
headerHover: css({
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
|
||||
|
||||
+6
-3
@@ -16,7 +16,6 @@ export interface OptionsPaneCategoryDescriptorProps {
|
||||
itemsCount?: number;
|
||||
customRender?: () => React.ReactNode;
|
||||
sandboxId?: string;
|
||||
isOpenable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,8 +59,12 @@ export class OptionsPaneCategoryDescriptor {
|
||||
return this.props.customRender();
|
||||
}
|
||||
|
||||
if (this.props.id === '') {
|
||||
return <Box padding={2}>{this.items.map((item) => item.render(searchQuery))}</Box>;
|
||||
if (this.props.title === '') {
|
||||
return (
|
||||
<Box padding={2} key={this.props.title}>
|
||||
{this.items.map((item) => item.render(searchQuery))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1068,23 +1068,18 @@
|
||||
"elements": {
|
||||
"dashboard": "Dashboard",
|
||||
"objects": "Objects",
|
||||
"panel": "Panel",
|
||||
"panels": "Panels",
|
||||
"row": "Row",
|
||||
"rows": "Rows",
|
||||
"tab": "Tab",
|
||||
"tabs": "Tabs"
|
||||
},
|
||||
"objects": {
|
||||
"multi-select": {
|
||||
"selection-number": "No. of objects selected: {{length}}"
|
||||
}
|
||||
},
|
||||
"open": "Open options pane",
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "Hide",
|
||||
"title": "Row header"
|
||||
},
|
||||
"multi-select": {
|
||||
"title": "{{length}} rows selected"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1165,13 +1160,11 @@
|
||||
"copy-or-duplicate": "Copy or Duplicate",
|
||||
"delete": "Delete",
|
||||
"duplicate": "Duplicate",
|
||||
"layout": "Layout",
|
||||
"panels-title": "{{length}} panels selected"
|
||||
"layout": "Layout"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"description": "Description",
|
||||
"title": "Dashboard options",
|
||||
"title-option": "Title"
|
||||
},
|
||||
"outline": {
|
||||
@@ -1214,7 +1207,6 @@
|
||||
},
|
||||
"rows-layout": {
|
||||
"description": "Rows layout",
|
||||
"item-name": "Row",
|
||||
"name": "Rows",
|
||||
"option": {
|
||||
"height": "Height",
|
||||
@@ -1253,9 +1245,6 @@
|
||||
"menu": {
|
||||
"move-tab": "Move tab"
|
||||
},
|
||||
"multi-select": {
|
||||
"title": "{{length}} tabs selected"
|
||||
},
|
||||
"name": "Tabs",
|
||||
"tab": {
|
||||
"menu": {
|
||||
@@ -1270,7 +1259,6 @@
|
||||
"new": "New tab"
|
||||
},
|
||||
"tab-options": {
|
||||
"title": "Tab",
|
||||
"title-option": "Title"
|
||||
}
|
||||
},
|
||||
@@ -1345,9 +1333,8 @@
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "Description",
|
||||
"open-edit": "Open Panel Edit",
|
||||
"open-edit": "Open panel editor",
|
||||
"plugin-type-image": "Image of plugin type",
|
||||
"title": "Panel",
|
||||
"title-option": "Title",
|
||||
"transparent-background": "Transparent background"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user