Rethinking how to add elements

adding-elements-rethink
Torkel Ödegaard 4 months ago
parent 169b0eb5f5
commit a4f6dfcdd5
  1. 1
      packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts
  2. 2
      public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx
  3. 47
      public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutRenderer.tsx
  4. 2
      public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx
  5. 19
      public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManagerRenderer.tsx
  6. 4
      public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx
  7. 92
      public/app/features/dashboard-scene/scene/layout-tabs/TabItemMenu.tsx
  8. 7
      public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManagerRenderer.tsx

@ -92,6 +92,7 @@ export function getDashboardGridStyles(theme: GrafanaTheme2) {
'.dashboard-canvas-add-button': { '.dashboard-canvas-add-button': {
opacity: 0, opacity: 0,
transition: theme.transitions.create('opacity'),
'&:hover': { '&:hover': {
opacity: 1, opacity: 1,

@ -57,7 +57,7 @@ export class ResponsiveGridLayoutManager
vizPanel.clearParent(); vizPanel.clearParent();
this.state.layout.setState({ this.state.layout.setState({
children: [new ResponsiveGridItem({ body: vizPanel }), ...this.state.layout.state.children], children: [...this.state.layout.state.children, new ResponsiveGridItem({ body: vizPanel })],
}); });
this.publishEvent(new NewObjectAddedToCanvasEvent(vizPanel), true); this.publishEvent(new NewObjectAddedToCanvasEvent(vizPanel), true);

@ -2,21 +2,23 @@ import { css, cx } from '@emotion/css';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { LazyLoader, SceneComponentProps } from '@grafana/scenes'; import { LazyLoader, SceneComponentProps, sceneGraph } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui'; import { Button, useStyles2 } from '@grafana/ui';
import { getDashboardSceneFor } from '../../utils/utils'; import { getDefaultVizPanel, useDashboardState } from '../../utils/utils';
import { ResponsiveGridLayout, ResponsiveGridLayoutState } from './ResponsiveGridLayout'; import { ResponsiveGridLayout, ResponsiveGridLayoutState } from './ResponsiveGridLayout';
import { ResponsiveGridLayoutManager } from './ResponsiveGridLayoutManager';
export function ResponsiveGridLayoutRenderer({ model }: SceneComponentProps<ResponsiveGridLayout>) { export function ResponsiveGridLayoutRenderer({ model }: SceneComponentProps<ResponsiveGridLayout>) {
const { children, isHidden, isLazy } = model.useState(); const { children, isHidden, isLazy } = model.useState();
const styles = useStyles2(getStyles, model.state); const styles = useStyles2(getStyles, model.state);
const { layoutOrchestrator } = getDashboardSceneFor(model).state; const { layoutOrchestrator, isEditing } = useDashboardState(model);
const { activeLayoutItemRef } = layoutOrchestrator.useState(); const { activeLayoutItemRef } = layoutOrchestrator.useState();
const activeLayoutItem = activeLayoutItemRef?.resolve(); const activeLayoutItem = activeLayoutItemRef?.resolve();
const currentLayoutIsActive = children.some((c) => c === activeLayoutItem); const currentLayoutIsActive = children.some((c) => c === activeLayoutItem);
const layoutManager = sceneGraph.getAncestor(model, ResponsiveGridLayoutManager);
useEffect(() => { useEffect(() => {
if (model.containerRef.current) { if (model.containerRef.current) {
@ -38,7 +40,7 @@ export function ResponsiveGridLayoutRenderer({ model }: SceneComponentProps<Resp
} }
return ( return (
<div className={styles.container} ref={model.containerRef}> <div className={cx(styles.container, isEditing && styles.containerEditing)} ref={model.containerRef}>
<div <div
style={{ style={{
gridRow: model.activeGridCell.row, gridRow: model.activeGridCell.row,
@ -72,6 +74,18 @@ export function ResponsiveGridLayoutRenderer({ model }: SceneComponentProps<Resp
</Wrapper> </Wrapper>
); );
})} })}
{isEditing && (
<div className={cx(styles.addAction, 'dashboard-canvas-add-button')}>
<Button
variant="primary"
fill="text"
icon="plus"
onClick={() => layoutManager.addPanel(getDefaultVizPanel())}
>
Add panel
</Button>
</div>
)}
</div> </div>
); );
} }
@ -89,7 +103,6 @@ const getStyles = (theme: GrafanaTheme2, state: ResponsiveGridLayoutState) => ({
alignItems: state.alignItems || 'unset', alignItems: state.alignItems || 'unset',
justifyContent: state.justifyContent || 'unset', justifyContent: state.justifyContent || 'unset',
flexGrow: 1, flexGrow: 1,
[theme.breakpoints.down('md')]: state.md [theme.breakpoints.down('md')]: state.md
? { ? {
gridTemplateRows: state.md.templateRows, gridTemplateRows: state.md.templateRows,
@ -101,6 +114,16 @@ const getStyles = (theme: GrafanaTheme2, state: ResponsiveGridLayoutState) => ({
justifyContent: state.md.justifyContent, justifyContent: state.md.justifyContent,
} }
: undefined, : undefined,
// Show add action when hovering over the grid
'&:hover': {
'.dashboard-canvas-add-button': {
opacity: 1,
},
},
}),
containerEditing: css({
paddingBottom: theme.spacing(5),
position: 'relative',
}), }),
wrapper: css({ wrapper: css({
display: 'grid', display: 'grid',
@ -108,6 +131,18 @@ const getStyles = (theme: GrafanaTheme2, state: ResponsiveGridLayoutState) => ({
width: '100%', width: '100%',
height: '100%', height: '100%',
}), }),
addAction: css({
position: 'absolute',
padding: theme.spacing(1, 0),
height: theme.spacing(5),
bottom: 0,
left: 0,
right: 0,
opacity: 0,
[theme.transitions.handleMotion('no-preference', 'reduce')]: {
transition: theme.transitions.create('opacity'),
},
}),
dragging: css({ dragging: css({
position: 'fixed', position: 'fixed',
top: 0, top: 0,

@ -91,7 +91,7 @@ export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) {
)} )}
</span> </span>
</button> </button>
{!isClone && isEditing && <RowItemMenu model={model} />} {/* {!isClone && isEditing && <RowItemMenu model={model} />} */}
</div> </div>
)} )}
{!isCollapsed && <layout.Component model={layout} />} {!isCollapsed && <layout.Component model={layout} />}

@ -2,12 +2,15 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { SceneComponentProps } from '@grafana/scenes'; import { SceneComponentProps } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui'; import { Button, useStyles2 } from '@grafana/ui';
import { useDashboardState } from '../../utils/utils';
import { RowsLayoutManager } from './RowsLayoutManager'; import { RowsLayoutManager } from './RowsLayoutManager';
export function RowLayoutManagerRenderer({ model }: SceneComponentProps<RowsLayoutManager>) { export function RowLayoutManagerRenderer({ model }: SceneComponentProps<RowsLayoutManager>) {
const { rows } = model.useState(); const { rows } = model.useState();
const { isEditing } = useDashboardState(model);
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
return ( return (
@ -15,6 +18,20 @@ export function RowLayoutManagerRenderer({ model }: SceneComponentProps<RowsLayo
{rows.map((row) => ( {rows.map((row) => (
<row.Component model={row} key={row.state.key!} /> <row.Component model={row} key={row.state.key!} />
))} ))}
{isEditing && (
<div className="dashboard-canvas-add-button">
<Button
aria-label="Add row"
title="Add row"
icon="plus"
variant="primary"
fill="text"
onClick={() => model.addNewRow()}
>
Add row
</Button>
</div>
)}
</div> </div>
); );
} }

@ -95,6 +95,10 @@ export class TabItem
this._getParentLayout().addTabAfter(this); this._getParentLayout().addTabAfter(this);
} }
public onAddTab() {
this._getParentLayout().addNewTab();
}
public onMoveLeft() { public onMoveLeft() {
this._getParentLayout().moveTabLeft(this); this._getParentLayout().moveTabLeft(this);
} }

@ -1,8 +1,5 @@
import { css } from '@emotion/css'; import { Button } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Dropdown, Menu, ToolbarButtonRow, useStyles2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { TabItem } from './TabItem'; import { TabItem } from './TabItem';
@ -11,80 +8,19 @@ interface Props {
} }
export function TabItemMenu({ model }: Props) { export function TabItemMenu({ model }: Props) {
const styles = useStyles2(getStyles);
return ( return (
<ToolbarButtonRow className={styles.container}> <div className="dashboard-canvas-add-button">
<Dropdown <Button
placement="bottom-end" aria-label={t('dashboard.tabs-layout.tab.menu.add', 'Add tab')}
overlay={() => ( title={t('dashboard.tabs-layout.tab.menu.add', 'Add tab')}
<Menu> tooltip={t('dashboard.tabs-layout.tab.menu.add', 'Add tab')}
<Menu.Item icon="plus"
ariaLabel={t('dashboard.tabs-layout.tab.menu.add-panel', 'Panel')} variant="primary"
label={t('dashboard.tabs-layout.tab.menu.add-panel', 'Panel')} fill="text"
onClick={() => model.onAddPanel()} onClick={() => model.onAddTab()}
/>
<Menu.Divider />
<Menu.Item
ariaLabel={t('dashboard.tabs-layout.tab.menu.add-tab-before', 'Tab before')}
label={t('dashboard.tabs-layout.tab.menu.add-tab-above', 'Tab before')}
onClick={() => model.onAddTabBefore()}
/>
<Menu.Item
ariaLabel={t('dashboard.tabs-layout.tab.menu.add-tab-after', 'Tab after')}
label={t('dashboard.tabs-layout.tab.menu.add-tab-after', 'Tab after')}
onClick={() => model.onAddTabAfter()}
/>
</Menu>
)}
>
<Button
aria-label={t('dashboard.tabs-layout.tab.menu.add', 'Add tab')}
title={t('dashboard.tabs-layout.tab.menu.add', 'Add tab')}
tooltip={t('dashboard.tabs-layout.tab.menu.add', 'Add tab')}
icon="plus"
variant="secondary"
size="sm"
>
<Trans i18nKey="grafana-ui.tags-input.add">Add</Trans>
</Button>
</Dropdown>
<Dropdown
placement="bottom-end"
overlay={() => (
<Menu>
<Menu.Item
aria-label={t('dashboard.tabs-layout.tab.menu.move-left', 'Move tab left')}
label={t('dashboard.tabs-layout.tab.menu.move-left', 'Move tab left')}
onClick={() => model.onMoveLeft()}
/>
<Menu.Divider />
<Menu.Item
aria-label={t('dashboard.tabs-layout.tab.menu.move-right', 'Move tab right')}
label={t('dashboard.tabs-layout.tab.menu.move-right', 'Move tab right')}
onClick={() => model.onMoveRight()}
/>
</Menu>
)}
> >
<Button New tab
aria-label={t('dashboard.tabs-layout.menu.move-tab', 'Move tab')} </Button>
title={t('dashboard.tabs-layout.menu.move-tab', 'Move tab')} </div>
tooltip={t('dashboard.tabs-layout.menu.move-tab', 'Move tab')}
icon="arrows-h"
variant="secondary"
size="sm"
/>
</Dropdown>
</ToolbarButtonRow>
); );
} }
function getStyles(theme: GrafanaTheme2) {
return {
container: css({
gap: theme.spacing(1),
flexShrink: 0,
}),
};
}

@ -47,11 +47,16 @@ const getStyles = (theme: GrafanaTheme2) => ({
}), }),
tabsBar: css({ tabsBar: css({
overflow: 'hidden', overflow: 'hidden',
// ':hover': {
// '.dashboard-canvas-add-button': {
// opacity: 1,
// },
// },
}), }),
tabsRow: css({ tabsRow: css({
justifyContent: 'space-between',
display: 'flex', display: 'flex',
width: '100%', width: '100%',
alignItems: 'center',
}), }),
tabsContainer: css({ tabsContainer: css({
display: 'flex', display: 'flex',

Loading…
Cancel
Save