Dashboards: Redesign edit pane of dashboard and viz panels (#101437)
* adjust options pane category component and shared logic * redesign dashboard edit pane * clean up * redesign viz panel edit panes * address comments * updated i18n * clean up * address comments
This commit is contained in:
@@ -6,7 +6,7 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { useLayoutCategory } from '../scene/layouts-shared/DashboardLayoutSelector';
|
||||
import { DashboardLayoutSelector } from '../scene/layouts-shared/DashboardLayoutSelector';
|
||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||
|
||||
export class DashboardEditableElement implements EditableDashboardElement {
|
||||
@@ -28,29 +28,29 @@ export class DashboardEditableElement implements EditableDashboardElement {
|
||||
return new OptionsPaneCategoryDescriptor({
|
||||
title: t('dashboard.options.title', 'Dashboard options'),
|
||||
id: 'dashboard-options',
|
||||
isOpenDefault: true,
|
||||
isOpenable: false,
|
||||
})
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.options.title-option', 'Title'),
|
||||
render: function renderTitle() {
|
||||
return <DashboardTitleInput dashboard={dashboard} />;
|
||||
},
|
||||
render: () => <DashboardTitleInput dashboard={dashboard} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.options.description', 'Description'),
|
||||
render: function renderTitle() {
|
||||
return <DashboardDescriptionInput dashboard={dashboard} />;
|
||||
},
|
||||
render: () => <DashboardDescriptionInput dashboard={dashboard} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.layout.common.layout', 'Layout'),
|
||||
render: () => <DashboardLayoutSelector layoutManager={body} />,
|
||||
})
|
||||
);
|
||||
}, [dashboard]);
|
||||
}, [body, dashboard]);
|
||||
|
||||
const layoutCategory = useLayoutCategory(body);
|
||||
|
||||
return [dashboardOptions, layoutCategory];
|
||||
return [dashboardOptions];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Dropdown, Button, IconButton, Menu, Stack, Icon } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
|
||||
interface EditPaneHeaderProps {
|
||||
title: string;
|
||||
onDelete?: () => void;
|
||||
onCopy?: () => void;
|
||||
onDuplicate?: () => void;
|
||||
}
|
||||
|
||||
export const EditPaneHeader = ({ title, onDelete, onCopy, onDuplicate }: EditPaneHeaderProps) => {
|
||||
const addCopyOrDuplicate = onCopy || onDuplicate;
|
||||
return (
|
||||
<Stack justifyContent="space-between" alignItems="center" width="100%">
|
||||
<span>{title}</span>
|
||||
<Stack alignItems="center">
|
||||
{addCopyOrDuplicate ? (
|
||||
<Dropdown overlay={<MenuItems onCopy={onCopy} onDuplicate={onDuplicate} />}>
|
||||
<Button
|
||||
tooltip={t('dashboard.layout.common.copy-or-duplicate', 'Copy or Duplicate')}
|
||||
tooltipPlacement="bottom"
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
size="md"
|
||||
>
|
||||
<Icon name="copy" /> <Icon name="angle-down" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
|
||||
<IconButton
|
||||
size="md"
|
||||
variant="secondary"
|
||||
onClick={onDelete}
|
||||
name="trash-alt"
|
||||
tooltip={t('dashboard.layout.common.delete', 'Delete')}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -24,6 +24,8 @@ export function ElementEditPane({ element }: Props) {
|
||||
title={elementInfo.name}
|
||||
isOpenDefault={true}
|
||||
className={styles.noBorderTop}
|
||||
renderTitle={element.renderTitle}
|
||||
isOpenable={element.isOpenable}
|
||||
>
|
||||
<div className={styles.actionsBox}>{element.renderActions()}</div>
|
||||
</OptionsPaneCategory>
|
||||
|
||||
+20
-20
@@ -1,14 +1,15 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { Button, Stack, Text } 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 { EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
|
||||
import { EditPaneHeader } from './EditPaneHeader';
|
||||
|
||||
export class MultiSelectedVizPanelsEditableElement implements MultiSelectedEditableDashboardElement {
|
||||
public readonly isMultiSelectedEditableDashboardElement = true;
|
||||
public readonly key: string;
|
||||
@@ -21,23 +22,22 @@ export class MultiSelectedVizPanelsEditableElement implements MultiSelectedEdita
|
||||
return { name: t('dashboard.edit-pane.elements.panels', 'Panels'), typeId: 'panels', icon: 'folder' };
|
||||
}
|
||||
|
||||
renderActions(): ReactNode {
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="dashboard.edit-pane.panels.multi-select.selection-number"
|
||||
values={{ length: this._panels.length }}
|
||||
>
|
||||
No. of panels 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>
|
||||
);
|
||||
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()}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
return [header];
|
||||
}
|
||||
|
||||
public onDelete() {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { GrafanaTheme2, textUtil } from '@grafana/data';
|
||||
import { sceneGraph, VizPanel } from '@grafana/scenes';
|
||||
import { Button } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import { useStyles2, Text, Icon, Stack, Tooltip } from '@grafana/ui';
|
||||
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 { getVisualizationOptions2 } from 'app/features/dashboard/components/PanelEditor/getVisualizationOptions';
|
||||
|
||||
import {
|
||||
PanelBackgroundSwitch,
|
||||
@@ -16,11 +17,16 @@ import { BulkActionElement } from '../scene/types/BulkActionElement';
|
||||
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 { EditPaneHeader } from './EditPaneHeader';
|
||||
|
||||
export class VizPanelEditableElement implements EditableDashboardElement, BulkActionElement {
|
||||
public readonly isEditableDashboardElement = true;
|
||||
public readonly typeName = 'Panel';
|
||||
|
||||
public constructor(private panel: VizPanel) {}
|
||||
public constructor(public panel: VizPanel) {}
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return {
|
||||
@@ -36,35 +42,38 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
|
||||
|
||||
const panelOptions = useMemo(() => {
|
||||
return new OptionsPaneCategoryDescriptor({
|
||||
title: t('dashboard.viz-panel.options.title', 'Panel options'),
|
||||
id: 'panel-options',
|
||||
isOpenDefault: true,
|
||||
title: ``,
|
||||
id: 'panel-header',
|
||||
isOpenable: false,
|
||||
renderTitle: () => (
|
||||
<EditPaneHeader title={t('dashboard.viz-panel.options.title', 'Panel')} onDelete={() => this.onDelete()} />
|
||||
),
|
||||
})
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: '',
|
||||
render: () => <OpenPanelEditViz panel={this.panel} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.viz-panel.options.title-option', 'Title'),
|
||||
value: panel.state.title,
|
||||
popularRank: 1,
|
||||
render: function renderTitle() {
|
||||
return <PanelFrameTitleInput panel={panel} />;
|
||||
},
|
||||
render: () => <PanelFrameTitleInput panel={panel} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.viz-panel.options.description', 'Description'),
|
||||
value: panel.state.description,
|
||||
render: function renderDescription() {
|
||||
return <PanelDescriptionTextArea panel={panel} />;
|
||||
},
|
||||
render: () => <PanelDescriptionTextArea panel={panel} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.viz-panel.options.transparent-background', 'Transparent background'),
|
||||
render: function renderTransparent() {
|
||||
return <PanelBackgroundSwitch panel={panel} />;
|
||||
},
|
||||
render: () => <PanelBackgroundSwitch panel={panel} />,
|
||||
})
|
||||
);
|
||||
}, [panel]);
|
||||
@@ -76,50 +85,73 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
|
||||
return undefined;
|
||||
}, [layoutElement]);
|
||||
|
||||
const { options, fieldConfig, _pluginInstanceState } = panel.useState();
|
||||
const dataProvider = sceneGraph.getData(panel);
|
||||
const { data } = dataProvider.useState();
|
||||
|
||||
const visualizationOptions = useMemo(() => {
|
||||
const plugin = panel.getPlugin();
|
||||
if (!plugin) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getVisualizationOptions2({
|
||||
panel,
|
||||
data,
|
||||
plugin: plugin,
|
||||
eventBus: panel.getPanelContext().eventBus,
|
||||
instanceState: _pluginInstanceState,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data, panel, options, fieldConfig, _pluginInstanceState]);
|
||||
|
||||
const categories = [panelOptions];
|
||||
if (layoutCategory) {
|
||||
categories.push(layoutCategory);
|
||||
}
|
||||
|
||||
categories.push(...visualizationOptions);
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
public onDelete = () => {
|
||||
public onDelete() {
|
||||
const layout = dashboardSceneGraph.getLayoutManagerFor(this.panel);
|
||||
layout.removePanel?.(this.panel);
|
||||
};
|
||||
|
||||
public renderActions(): ReactNode {
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" variant="secondary">
|
||||
<Trans i18nKey="panel.header-menu.edit">Edit</Trans>
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" icon="copy" />
|
||||
<Button size="sm" variant="destructive" fill="outline" onClick={this.onDelete} icon="trash-alt" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type OpenPanelEditVizProps = {
|
||||
panel: VizPanel;
|
||||
};
|
||||
|
||||
const OpenPanelEditViz = ({ panel }: OpenPanelEditVizProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const plugin = panel.getPlugin();
|
||||
const imgSrc = plugin?.meta.info.logos.small;
|
||||
|
||||
return (
|
||||
<Stack alignItems="center" width="100%">
|
||||
{plugin ? (
|
||||
<Tooltip content={t('dashboard.viz-panel.options.open-edit', 'Open Panel Edit')}>
|
||||
<a
|
||||
href={textUtil.sanitizeUrl(getEditPanelUrl(getPanelIdForVizPanel(panel)))}
|
||||
className={cx(styles.pluginDescriptionWrapper)}
|
||||
>
|
||||
<img
|
||||
className={styles.panelVizImg}
|
||||
src={imgSrc}
|
||||
alt={t('dashboard.viz-panel.options.plugin-type-image', 'Image of plugin type')}
|
||||
/>
|
||||
<Text truncate>{plugin.meta.name}</Text>
|
||||
<Icon className={styles.panelVizIcon} name="sliders-v-alt" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
pluginDescriptionWrapper: css({
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'center',
|
||||
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}`,
|
||||
borderRadius: theme.shape.radius.default,
|
||||
paddingInline: theme.spacing(1),
|
||||
paddingBlock: theme.spacing(0.5),
|
||||
flexGrow: 1,
|
||||
}),
|
||||
panelVizImg: css({
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
marginRight: theme.spacing(1),
|
||||
}),
|
||||
panelVizIcon: css({
|
||||
marginLeft: 'auto',
|
||||
}),
|
||||
});
|
||||
|
||||
+3
-4
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { Select } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
@@ -55,10 +56,8 @@ export function useLayoutCategory(layoutManager: DashboardLayoutManager) {
|
||||
|
||||
layoutCategory.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: 'Type',
|
||||
render: function renderTitle() {
|
||||
return <DashboardLayoutSelector layoutManager={layoutManager} />;
|
||||
},
|
||||
title: t('dashboard.layout.common.layout', 'Layout'),
|
||||
render: () => <DashboardLayoutSelector layoutManager={layoutManager} />,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -32,6 +32,16 @@ export interface EditableDashboardElement {
|
||||
* 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>;
|
||||
}
|
||||
|
||||
export interface EditableDashboardElementInfo {
|
||||
|
||||
+10
@@ -27,6 +27,16 @@ export interface MultiSelectedEditableDashboardElement {
|
||||
* 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 {
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface OptionsPaneCategoryProps {
|
||||
isNested?: boolean;
|
||||
children: ReactNode;
|
||||
sandboxId?: string;
|
||||
isOpenable?: boolean;
|
||||
}
|
||||
|
||||
const CATEGORY_PARAM_NAME = 'showCategory' as const;
|
||||
@@ -37,12 +38,13 @@ export const OptionsPaneCategory = React.memo(
|
||||
itemsCount,
|
||||
isNested = false,
|
||||
sandboxId,
|
||||
isOpenable = true,
|
||||
}: OptionsPaneCategoryProps) => {
|
||||
const [savedState, setSavedState] = useLocalStorage(getOptionGroupStorageKey(id), {
|
||||
isExpanded: isOpenDefault,
|
||||
});
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? isOpenDefault);
|
||||
const [isExpanded, setIsExpanded] = useState(!isOpenable || (savedState?.isExpanded ?? isOpenDefault));
|
||||
const manualClickTime = useRef(0);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [queryParams, updateQueryParams] = useQueryParams();
|
||||
@@ -66,6 +68,9 @@ export const OptionsPaneCategory = React.memo(
|
||||
}, [forceOpen, isExpanded, isOpenFromUrl]);
|
||||
|
||||
const onToggle = useCallback(() => {
|
||||
if (!isOpenable) {
|
||||
return;
|
||||
}
|
||||
manualClickTime.current = Date.now();
|
||||
updateQueryParams(
|
||||
{
|
||||
@@ -75,7 +80,7 @@ export const OptionsPaneCategory = React.memo(
|
||||
);
|
||||
setSavedState({ isExpanded: !isExpanded });
|
||||
setIsExpanded(!isExpanded);
|
||||
}, [setSavedState, setIsExpanded, updateQueryParams, isExpanded, id]);
|
||||
}, [isOpenable, updateQueryParams, isExpanded, id, setSavedState]);
|
||||
|
||||
if (!renderTitle) {
|
||||
renderTitle = function defaultTitle(isExpanded: boolean) {
|
||||
@@ -101,6 +106,7 @@ export const OptionsPaneCategory = React.memo(
|
||||
);
|
||||
|
||||
const headerStyles = cx(styles.header, {
|
||||
[styles.headerHover]: isOpenable,
|
||||
[styles.headerExpanded]: isExpanded,
|
||||
[styles.headerNested]: isNested,
|
||||
});
|
||||
@@ -123,17 +129,19 @@ export const OptionsPaneCategory = React.memo(
|
||||
<h6 id={`button-${id}`} className={styles.title}>
|
||||
{renderTitle(isExpanded)}
|
||||
</h6>
|
||||
<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}
|
||||
/>
|
||||
{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}
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<div className={bodyStyles} id={id} aria-labelledby={`button-${id}`}>
|
||||
@@ -161,15 +169,19 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
fontSize: '1rem',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
margin: 0,
|
||||
height: theme.spacing(4),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
header: css({
|
||||
display: 'flex',
|
||||
cursor: 'pointer',
|
||||
alignItems: 'center',
|
||||
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),
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface OptionsPaneCategoryDescriptorProps {
|
||||
itemsCount?: number;
|
||||
customRender?: () => React.ReactNode;
|
||||
sandboxId?: string;
|
||||
isOpenable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1078,11 +1078,6 @@
|
||||
}
|
||||
},
|
||||
"open": "Open options pane",
|
||||
"panels": {
|
||||
"multi-select": {
|
||||
"selection-number": "No. of panels selected: {{length}}"
|
||||
}
|
||||
},
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "Hide",
|
||||
@@ -1171,6 +1166,16 @@
|
||||
"rows": "Total number rows",
|
||||
"table-title": "Stats"
|
||||
},
|
||||
"layout": {
|
||||
"common": {
|
||||
"copy": "Copy",
|
||||
"copy-or-duplicate": "Copy or Duplicate",
|
||||
"delete": "Delete",
|
||||
"duplicate": "Duplicate",
|
||||
"layout": "Layout",
|
||||
"panels-title": "{{length}} Panels Selected"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"description": "Description",
|
||||
"title": "Dashboard options",
|
||||
@@ -1325,7 +1330,9 @@
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "Description",
|
||||
"title": "Panel options",
|
||||
"open-edit": "Open Panel Edit",
|
||||
"plugin-type-image": "Image of plugin type",
|
||||
"title": "Panel",
|
||||
"title-option": "Title",
|
||||
"transparent-background": "Transparent background"
|
||||
}
|
||||
|
||||
@@ -1078,11 +1078,6 @@
|
||||
}
|
||||
},
|
||||
"open": "Øpęʼn őpŧįőʼnş päʼnę",
|
||||
"panels": {
|
||||
"multi-select": {
|
||||
"selection-number": "Ńő. őƒ päʼnęľş şęľęčŧęđ: {{length}}"
|
||||
}
|
||||
},
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "Ħįđę",
|
||||
@@ -1171,6 +1166,16 @@
|
||||
"rows": "Ŧőŧäľ ʼnūmþęř řőŵş",
|
||||
"table-title": "Ŝŧäŧş"
|
||||
},
|
||||
"layout": {
|
||||
"common": {
|
||||
"copy": "Cőpy",
|
||||
"copy-or-duplicate": "Cőpy őř Đūpľįčäŧę",
|
||||
"delete": "Đęľęŧę",
|
||||
"duplicate": "Đūpľįčäŧę",
|
||||
"layout": "Ŀäyőūŧ",
|
||||
"panels-title": "{{length}} Päʼnęľş Ŝęľęčŧęđ"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"description": "Đęşčřįpŧįőʼn",
|
||||
"title": "Đäşĥþőäřđ őpŧįőʼnş",
|
||||
@@ -1325,7 +1330,9 @@
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "Đęşčřįpŧįőʼn",
|
||||
"title": "Päʼnęľ őpŧįőʼnş",
|
||||
"open-edit": "Øpęʼn Päʼnęľ Ēđįŧ",
|
||||
"plugin-type-image": "Ĩmäģę őƒ pľūģįʼn ŧypę",
|
||||
"title": "Päʼnęľ",
|
||||
"title-option": "Ŧįŧľę",
|
||||
"transparent-background": "Ŧřäʼnşpäřęʼnŧ þäčĸģřőūʼnđ"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user