mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 18:44:54 +08:00
Dashboard: New UX for switching layouts (#102268)
* Layout switching * Update * Update * Update * Update * Update * unline styles * fixing lint issue
This commit is contained in:
@ -61,6 +61,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
height: theme.spacing(2),
|
height: theme.spacing(2),
|
||||||
border: `1px solid ${theme.colors.border.medium}`,
|
border: `1px solid ${theme.colors.border.medium}`,
|
||||||
borderRadius: theme.shape.radius.circle,
|
borderRadius: theme.shape.radius.circle,
|
||||||
|
cursor: 'pointer',
|
||||||
margin: '3px 0' /* Space for box-shadow when focused */,
|
margin: '3px 0' /* Space for box-shadow when focused */,
|
||||||
|
|
||||||
':checked': {
|
':checked': {
|
||||||
@ -100,6 +101,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: `${theme.spacing(2)} auto`,
|
gridTemplateColumns: `${theme.spacing(2)} auto`,
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
|
cursor: 'pointer',
|
||||||
}),
|
}),
|
||||||
description: css({
|
description: css({
|
||||||
fontSize: theme.typography.size.sm,
|
fontSize: theme.typography.size.sm,
|
||||||
|
@ -6,7 +6,7 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
|
|||||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||||
|
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { DashboardLayoutSelector } from '../scene/layouts-shared/DashboardLayoutSelector';
|
import { useLayoutCategory } from '../scene/layouts-shared/DashboardLayoutSelector';
|
||||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||||
|
|
||||||
export class DashboardEditableElement implements EditableDashboardElement {
|
export class DashboardEditableElement implements EditableDashboardElement {
|
||||||
@ -41,24 +41,14 @@ export class DashboardEditableElement implements EditableDashboardElement {
|
|||||||
title: t('dashboard.options.description', 'Description'),
|
title: t('dashboard.options.description', 'Description'),
|
||||||
render: () => <DashboardDescriptionInput dashboard={dashboard} />,
|
render: () => <DashboardDescriptionInput dashboard={dashboard} />,
|
||||||
})
|
})
|
||||||
)
|
|
||||||
.addItem(
|
|
||||||
new OptionsPaneItemDescriptor({
|
|
||||||
title: t('dashboard.layout.common.layout', 'Layout'),
|
|
||||||
render: () => <DashboardLayoutSelector layoutManager={body} />,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (body.getOptions) {
|
|
||||||
for (const option of body.getOptions()) {
|
|
||||||
editPaneHeaderOptions.addItem(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return editPaneHeaderOptions;
|
return editPaneHeaderOptions;
|
||||||
}, [body, dashboard]);
|
}, [dashboard]);
|
||||||
|
|
||||||
return [dashboardOptions];
|
const layoutCategory = useLayoutCategory(body);
|
||||||
|
|
||||||
|
return [dashboardOptions, layoutCategory];
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderActions(): ReactNode {
|
public renderActions(): ReactNode {
|
||||||
|
@ -51,11 +51,12 @@ export class DefaultGridLayoutManager
|
|||||||
return t('dashboard.default-layout.name', 'Custom');
|
return t('dashboard.default-layout.name', 'Custom');
|
||||||
},
|
},
|
||||||
get description() {
|
get description() {
|
||||||
return t('dashboard.default-layout.description', 'Manually size and position panels');
|
return t('dashboard.default-layout.description', 'Position and size each panel individually');
|
||||||
},
|
},
|
||||||
id: 'default-grid',
|
id: 'default-grid',
|
||||||
createFromLayout: DefaultGridLayoutManager.createFromLayout,
|
createFromLayout: DefaultGridLayoutManager.createFromLayout,
|
||||||
kind: 'GridLayout',
|
kind: 'GridLayout',
|
||||||
|
isGridLayout: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
public readonly descriptor = DefaultGridLayoutManager.descriptor;
|
public readonly descriptor = DefaultGridLayoutManager.descriptor;
|
||||||
|
@ -26,15 +26,15 @@ export class ResponsiveGridLayoutManager
|
|||||||
|
|
||||||
public static readonly descriptor: LayoutRegistryItem = {
|
public static readonly descriptor: LayoutRegistryItem = {
|
||||||
get name() {
|
get name() {
|
||||||
return t('dashboard.responsive-layout.name', 'Auto');
|
return t('dashboard.responsive-layout.name', 'Auto grid');
|
||||||
},
|
},
|
||||||
get description() {
|
get description() {
|
||||||
return t('dashboard.responsive-layout.description', 'Automatically positions panels into a grid.');
|
return t('dashboard.responsive-layout.description', 'Panels resize to fit and form uniform grids');
|
||||||
},
|
},
|
||||||
id: 'responsive-grid',
|
id: 'responsive-grid',
|
||||||
createFromLayout: ResponsiveGridLayoutManager.createFromLayout,
|
createFromLayout: ResponsiveGridLayoutManager.createFromLayout,
|
||||||
|
|
||||||
kind: 'ResponsiveGridLayout',
|
kind: 'ResponsiveGridLayout',
|
||||||
|
isGridLayout: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
public readonly descriptor = ResponsiveGridLayoutManager.descriptor;
|
public readonly descriptor = ResponsiveGridLayoutManager.descriptor;
|
||||||
|
@ -12,7 +12,7 @@ import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSou
|
|||||||
|
|
||||||
import { useConditionalRenderingEditor } from '../../conditional-rendering/ConditionalRenderingEditor';
|
import { useConditionalRenderingEditor } from '../../conditional-rendering/ConditionalRenderingEditor';
|
||||||
import { getQueryRunnerFor, useDashboard } from '../../utils/utils';
|
import { getQueryRunnerFor, useDashboard } from '../../utils/utils';
|
||||||
import { DashboardLayoutSelector } from '../layouts-shared/DashboardLayoutSelector';
|
import { useLayoutCategory } from '../layouts-shared/DashboardLayoutSelector';
|
||||||
import { useEditPaneInputAutoFocus } from '../layouts-shared/utils';
|
import { useEditPaneInputAutoFocus } from '../layouts-shared/utils';
|
||||||
|
|
||||||
import { RowItem } from './RowItem';
|
import { RowItem } from './RowItem';
|
||||||
@ -32,20 +32,8 @@ export function getEditOptions(model: RowItem): OptionsPaneCategoryDescriptor[]
|
|||||||
title: t('dashboard.rows-layout.option.height', 'Height'),
|
title: t('dashboard.rows-layout.option.height', 'Height'),
|
||||||
render: () => <RowHeightSelect row={model} />,
|
render: () => <RowHeightSelect row={model} />,
|
||||||
})
|
})
|
||||||
)
|
|
||||||
.addItem(
|
|
||||||
new OptionsPaneItemDescriptor({
|
|
||||||
title: t('dashboard.layout.common.layout', 'Layout'),
|
|
||||||
render: () => <DashboardLayoutSelector layoutManager={layout} />,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (layout.getOptions) {
|
|
||||||
for (const option of layout.getOptions()) {
|
|
||||||
editPaneHeaderOptions.addItem(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editPaneHeaderOptions
|
editPaneHeaderOptions
|
||||||
.addItem(
|
.addItem(
|
||||||
new OptionsPaneItemDescriptor({
|
new OptionsPaneItemDescriptor({
|
||||||
@ -61,13 +49,15 @@ export function getEditOptions(model: RowItem): OptionsPaneCategoryDescriptor[]
|
|||||||
);
|
);
|
||||||
|
|
||||||
return editPaneHeaderOptions;
|
return editPaneHeaderOptions;
|
||||||
}, [layout, model]);
|
}, [model]);
|
||||||
|
|
||||||
|
const layoutCategory = useLayoutCategory(layout);
|
||||||
|
|
||||||
const conditionalRenderingOptions = useMemo(() => {
|
const conditionalRenderingOptions = useMemo(() => {
|
||||||
return useConditionalRenderingEditor(model.state.conditionalRendering);
|
return useConditionalRenderingEditor(model.state.conditionalRendering);
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
const editOptions = [rowOptions];
|
const editOptions = [rowOptions, layoutCategory];
|
||||||
|
|
||||||
if (conditionalRenderingOptions) {
|
if (conditionalRenderingOptions) {
|
||||||
editOptions.push(conditionalRenderingOptions);
|
editOptions.push(conditionalRenderingOptions);
|
||||||
|
@ -29,12 +29,12 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
|
|||||||
return t('dashboard.rows-layout.name', 'Rows');
|
return t('dashboard.rows-layout.name', 'Rows');
|
||||||
},
|
},
|
||||||
get description() {
|
get description() {
|
||||||
return t('dashboard.rows-layout.description', 'Rows layout');
|
return t('dashboard.rows-layout.description', 'Collapsable panel groups with headings');
|
||||||
},
|
},
|
||||||
id: 'rows-layout',
|
id: 'rows-layout',
|
||||||
createFromLayout: RowsLayoutManager.createFromLayout,
|
createFromLayout: RowsLayoutManager.createFromLayout,
|
||||||
|
|
||||||
kind: 'RowsLayout',
|
kind: 'RowsLayout',
|
||||||
|
isGridLayout: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public readonly descriptor = RowsLayoutManager.descriptor;
|
public readonly descriptor = RowsLayoutManager.descriptor;
|
||||||
|
@ -30,11 +30,12 @@ export class TabsLayoutManager extends SceneObjectBase<TabsLayoutManagerState> i
|
|||||||
return t('dashboard.tabs-layout.name', 'Tabs');
|
return t('dashboard.tabs-layout.name', 'Tabs');
|
||||||
},
|
},
|
||||||
get description() {
|
get description() {
|
||||||
return t('dashboard.tabs-layout.description', 'Tabs layout');
|
return t('dashboard.tabs-layout.description', 'Organize panels into horizontal tabs');
|
||||||
},
|
},
|
||||||
id: 'tabs-layout',
|
id: 'tabs-layout',
|
||||||
createFromLayout: TabsLayoutManager.createFromLayout,
|
createFromLayout: TabsLayoutManager.createFromLayout,
|
||||||
kind: 'TabsLayout',
|
kind: 'TabsLayout',
|
||||||
|
isGridLayout: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public readonly descriptor = TabsLayoutManager.descriptor;
|
public readonly descriptor = TabsLayoutManager.descriptor;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { css, cx } from '@emotion/css';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { Select } from '@grafana/ui';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { RadioButtonDot, Stack, useStyles2, Text } from '@grafana/ui';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||||
@ -10,53 +12,171 @@ import { isLayoutParent } from '../types/LayoutParent';
|
|||||||
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
|
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
|
||||||
|
|
||||||
import { layoutRegistry } from './layoutRegistry';
|
import { layoutRegistry } from './layoutRegistry';
|
||||||
import { findParentLayout } from './utils';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
layoutManager: DashboardLayoutManager;
|
layoutManager: DashboardLayoutManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DashboardLayoutSelector({ layoutManager }: Props) {
|
export function DashboardLayoutSelector({ layoutManager }: Props) {
|
||||||
const options = useMemo(() => {
|
const isGridLayout = layoutManager.descriptor.isGridLayout;
|
||||||
const parentLayout = findParentLayout(layoutManager);
|
const options = layoutRegistry.list().filter((layout) => layout.isGridLayout === isGridLayout);
|
||||||
const parentLayoutId = parentLayout?.descriptor.id;
|
|
||||||
|
|
||||||
return layoutRegistry
|
const styles = useStyles2(getStyles);
|
||||||
.list()
|
|
||||||
.filter((layout) => layout.id !== parentLayoutId)
|
|
||||||
.map((layout) => ({
|
|
||||||
label: layout.name,
|
|
||||||
value: layout,
|
|
||||||
}));
|
|
||||||
}, [layoutManager]);
|
|
||||||
|
|
||||||
const currentLayoutId = layoutManager.descriptor.id;
|
|
||||||
const currentOption = options.find((option) => option.value.id === currentLayoutId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<div role="radiogroup" className={styles.radioGroup}>
|
||||||
options={options}
|
{options.map((opt) => {
|
||||||
value={currentOption}
|
switch (opt.id) {
|
||||||
onChange={(option) => {
|
case 'rows-layout':
|
||||||
if (option.value?.id !== currentOption?.value.id) {
|
return (
|
||||||
changeLayoutTo(layoutManager, option.value!);
|
<LayoutRadioButton
|
||||||
|
label={opt.name}
|
||||||
|
id={opt.id}
|
||||||
|
description={opt.description!}
|
||||||
|
isSelected={layoutManager.descriptor.id === opt.id}
|
||||||
|
onSelect={() => changeLayoutTo(layoutManager, opt)}
|
||||||
|
>
|
||||||
|
<div className={styles.rowsLayoutViz}>
|
||||||
|
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
|
||||||
|
<div style={{ gridColumn: 'span 3', fontSize: '6px' }}>⌄ .-.-.-.-.-</div>
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
|
||||||
|
<div style={{ gridColumn: 'span 3', fontSize: '6px' }}>⌄ .-.-.-.-.-</div>
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
</div>
|
||||||
|
</LayoutRadioButton>
|
||||||
|
);
|
||||||
|
case 'tabs-layout':
|
||||||
|
return (
|
||||||
|
<LayoutRadioButton
|
||||||
|
label={opt.name}
|
||||||
|
id={opt.id}
|
||||||
|
description={opt.description!}
|
||||||
|
isSelected={layoutManager.descriptor.id === opt.id}
|
||||||
|
onSelect={() => changeLayoutTo(layoutManager, opt)}
|
||||||
|
>
|
||||||
|
<Stack direction="column" gap={0.5} height={'100%'}>
|
||||||
|
<div className={styles.tabsBar}>
|
||||||
|
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
|
||||||
|
<div className={cx(styles.tab, styles.tabActive)}>-.-.-</div>
|
||||||
|
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
|
||||||
|
<div className={styles.tab}>-.-.-</div>
|
||||||
|
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
|
||||||
|
<div className={styles.tab}>-.-.-</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.tabsVizTabContent}>
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</LayoutRadioButton>
|
||||||
|
);
|
||||||
|
case 'responsive-grid':
|
||||||
|
return (
|
||||||
|
<LayoutRadioButton
|
||||||
|
label={opt.name}
|
||||||
|
id={opt.id}
|
||||||
|
description={opt.description!}
|
||||||
|
isSelected={layoutManager.descriptor.id === opt.id}
|
||||||
|
onSelect={() => changeLayoutTo(layoutManager, opt)}
|
||||||
|
>
|
||||||
|
<div className={styles.autoGridViz}>
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
</div>
|
||||||
|
</LayoutRadioButton>
|
||||||
|
);
|
||||||
|
case 'custom-grid':
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<LayoutRadioButton
|
||||||
|
label={opt.name}
|
||||||
|
id={opt.id}
|
||||||
|
description={opt.description!}
|
||||||
|
isSelected={layoutManager.descriptor.id === opt.id}
|
||||||
|
onSelect={() => changeLayoutTo(layoutManager, opt)}
|
||||||
|
>
|
||||||
|
<div className={styles.customGridViz}>
|
||||||
|
<GridCell colSpan={2} />
|
||||||
|
<div className={styles.customGridVizInner}>
|
||||||
|
<GridCell />
|
||||||
|
<GridCell />
|
||||||
|
</div>
|
||||||
|
<GridCell />
|
||||||
|
<GridCell colSpan={2} />
|
||||||
|
</div>
|
||||||
|
</LayoutRadioButton>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
})}
|
||||||
/>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LayoutRadioButtonProps {
|
||||||
|
label: string;
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
isSelected: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LayoutRadioButton({ label, id, description, isSelected, children, onSelect }: LayoutRadioButtonProps) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// This outer div is just so that the radio dot can be outside the
|
||||||
|
// label (as the RadioButtonDot has a label element and they can't nest)
|
||||||
|
<div className={styles.radioButtonOuter}>
|
||||||
|
<label
|
||||||
|
htmlFor={`layout-${id}`}
|
||||||
|
tabIndex={0}
|
||||||
|
className={cx(styles.radioButton, isSelected && styles.radioButtonActive)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<Stack direction="column" gap={1} justifyContent="space-between" grow={1}>
|
||||||
|
<Text weight="medium">{label}</Text>
|
||||||
|
<Text variant="bodySmall" color="secondary">
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</label>
|
||||||
|
<div className={styles.radioDot}>
|
||||||
|
<RadioButtonDot id={`layout-${id}`} name={'layout'} label={<></>} onChange={onSelect} checked={isSelected} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GridCell({ colSpan = 1 }: { colSpan?: number }) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
return <div className={styles.gridCell} style={{ gridColumn: `span ${colSpan}` }}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
export function useLayoutCategory(layoutManager: DashboardLayoutManager) {
|
export function useLayoutCategory(layoutManager: DashboardLayoutManager) {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
const categoryName = layoutManager.descriptor.isGridLayout
|
||||||
|
? t('dashboard.layout.common.grid', 'Grid')
|
||||||
|
: t('dashboard.layout.common.layout', 'Layout');
|
||||||
|
|
||||||
const layoutCategory = new OptionsPaneCategoryDescriptor({
|
const layoutCategory = new OptionsPaneCategoryDescriptor({
|
||||||
title: 'Layout',
|
title: categoryName,
|
||||||
id: 'layout-options',
|
id: 'layout-options',
|
||||||
isOpenDefault: true,
|
isOpenDefault: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
layoutCategory.addItem(
|
layoutCategory.addItem(
|
||||||
new OptionsPaneItemDescriptor({
|
new OptionsPaneItemDescriptor({
|
||||||
title: t('dashboard.layout.common.layout', 'Layout'),
|
title: '',
|
||||||
|
skipField: true,
|
||||||
render: () => <DashboardLayoutSelector layoutManager={layoutManager} />,
|
render: () => <DashboardLayoutSelector layoutManager={layoutManager} />,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -77,3 +197,100 @@ function changeLayoutTo(currentLayout: DashboardLayoutManager, newLayoutDescript
|
|||||||
layoutParent.switchLayout(newLayoutDescriptor.createFromLayout(currentLayout));
|
layoutParent.switchLayout(newLayoutDescriptor.createFromLayout(currentLayout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
radioButtonOuter: css({
|
||||||
|
position: 'relative',
|
||||||
|
}),
|
||||||
|
radioDot: css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: theme.spacing(0.5),
|
||||||
|
right: theme.spacing(0),
|
||||||
|
}),
|
||||||
|
radioGroup: css({
|
||||||
|
backgroundColor: theme.colors.background.primary,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}),
|
||||||
|
radioButton: css({
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: theme.spacing(1.5),
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
border: `1px solid ${theme.colors.border.weak}`,
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: theme.shape.radius.default,
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: `80px 1fr`,
|
||||||
|
gridTemplateRows: '70px',
|
||||||
|
}),
|
||||||
|
radioButtonActive: css({
|
||||||
|
border: `1px solid ${theme.colors.primary.border}`,
|
||||||
|
}),
|
||||||
|
gridCell: css({
|
||||||
|
backgroundColor: theme.colors.background.secondary,
|
||||||
|
border: `1px solid ${theme.colors.border.medium}`,
|
||||||
|
}),
|
||||||
|
tab: css({
|
||||||
|
width: theme.spacing(2),
|
||||||
|
height: theme.spacing(1),
|
||||||
|
fontSize: '5px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}),
|
||||||
|
tabActive: css({
|
||||||
|
'&:before': {
|
||||||
|
content: '" "',
|
||||||
|
position: 'absolute',
|
||||||
|
height: 1,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
background: theme.colors.gradients.brandHorizontal,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
tabsBar: css({
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(0.5),
|
||||||
|
borderBottom: `1px solid ${theme.colors.border.medium}`,
|
||||||
|
}),
|
||||||
|
rowsLayoutViz: css({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gridTemplateRows: '10px 1fr 10px 1fr',
|
||||||
|
gap: '4px',
|
||||||
|
height: '100%',
|
||||||
|
}),
|
||||||
|
tabsVizTabContent: css({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr',
|
||||||
|
gridTemplateRows: '1fr',
|
||||||
|
gap: '4px',
|
||||||
|
flexGrow: 1,
|
||||||
|
}),
|
||||||
|
autoGridViz: css({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||||
|
gridTemplateRows: 'repeat(2, 1fr)',
|
||||||
|
gap: '4px',
|
||||||
|
height: '100%',
|
||||||
|
}),
|
||||||
|
customGridViz: css({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gridTemplateRows: 'repeat(2, 1fr)',
|
||||||
|
gap: '4px',
|
||||||
|
height: '100%',
|
||||||
|
}),
|
||||||
|
customGridVizInner: css({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(1, 1fr)',
|
||||||
|
gridTemplateRows: 'repeat(2, 1fr)',
|
||||||
|
gap: '4px',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { SceneGridRow } from '@grafana/scenes';
|
import { SceneGridRow } from '@grafana/scenes';
|
||||||
|
|
||||||
import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared';
|
import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared';
|
||||||
@ -31,13 +32,20 @@ export function addNewTabTo(layout: DashboardLayoutManager): TabItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function addNewRowTo(layout: DashboardLayoutManager): RowItem | SceneGridRow {
|
export function addNewRowTo(layout: DashboardLayoutManager): RowItem | SceneGridRow {
|
||||||
if (layout instanceof RowsLayoutManager) {
|
/**
|
||||||
const row = layout.addNewRow();
|
* If new layouts feature is disabled we add old school rows to the custom grid layout
|
||||||
layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true);
|
*/
|
||||||
return row;
|
if (!config.featureToggles.dashboardNewLayouts) {
|
||||||
|
if (layout instanceof DefaultGridLayoutManager) {
|
||||||
|
const row = layout.addNewRow();
|
||||||
|
layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true);
|
||||||
|
return row;
|
||||||
|
} else {
|
||||||
|
throw new Error('New dashboard layouts feature not enabled but new layout found');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layout instanceof DefaultGridLayoutManager) {
|
if (layout instanceof RowsLayoutManager) {
|
||||||
const row = layout.addNewRow();
|
const row = layout.addNewRow();
|
||||||
layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true);
|
layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true);
|
||||||
return row;
|
return row;
|
||||||
@ -48,6 +56,9 @@ export function addNewRowTo(layout: DashboardLayoutManager): RowItem | SceneGrid
|
|||||||
return addNewRowTo(currentTab.state.layout);
|
return addNewRowTo(currentTab.state.layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we want to add a row and current layout is custom grid or auto we migrate to rows layout
|
||||||
|
// And wrap current layout in a row
|
||||||
|
|
||||||
const layoutParent = layout.parent!;
|
const layoutParent = layout.parent!;
|
||||||
if (!isLayoutParent(layoutParent)) {
|
if (!isLayoutParent(layoutParent)) {
|
||||||
throw new Error('Parent layout is not a LayoutParent');
|
throw new Error('Parent layout is not a LayoutParent');
|
||||||
|
@ -23,4 +23,9 @@ export interface LayoutRegistryItem<S = {}> extends RegistryItem {
|
|||||||
* Schema kind of layout
|
* Schema kind of layout
|
||||||
*/
|
*/
|
||||||
kind?: DashboardV2Spec['layout']['kind'];
|
kind?: DashboardV2Spec['layout']['kind'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is grid layout (that contains panels)
|
||||||
|
*/
|
||||||
|
isGridLayout: boolean;
|
||||||
}
|
}
|
||||||
|
@ -1066,7 +1066,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"default-layout": {
|
"default-layout": {
|
||||||
"description": "Manually size and position panels",
|
"description": "Position and size each panel individually",
|
||||||
"item-options": {
|
"item-options": {
|
||||||
"repeat": {
|
"repeat": {
|
||||||
"direction": {
|
"direction": {
|
||||||
@ -1215,6 +1215,7 @@
|
|||||||
"copy-or-duplicate": "Copy or Duplicate",
|
"copy-or-duplicate": "Copy or Duplicate",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
|
"grid": "Grid",
|
||||||
"layout": "Layout"
|
"layout": "Layout"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1238,8 +1239,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"responsive-layout": {
|
"responsive-layout": {
|
||||||
"description": "Automatically positions panels into a grid.",
|
"description": "Panels resize to fit and form uniform grids",
|
||||||
"name": "Auto",
|
"name": "Auto grid",
|
||||||
"options": {
|
"options": {
|
||||||
"columns": "Columns",
|
"columns": "Columns",
|
||||||
"fixed": "Fixed: {{size}}px",
|
"fixed": "Fixed: {{size}}px",
|
||||||
@ -1251,7 +1252,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rows-layout": {
|
"rows-layout": {
|
||||||
"description": "Rows layout",
|
"description": "Collapsable panel groups with headings",
|
||||||
"name": "Rows",
|
"name": "Rows",
|
||||||
"option": {
|
"option": {
|
||||||
"height": "Height",
|
"height": "Height",
|
||||||
@ -1286,7 +1287,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tabs-layout": {
|
"tabs-layout": {
|
||||||
"description": "Tabs layout",
|
"description": "Organize panels into horizontal tabs",
|
||||||
"menu": {
|
"menu": {
|
||||||
"move-tab": "Move tab"
|
"move-tab": "Move tab"
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user