Files
grafana/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx
Torkel Ödegaard 06d0d41183 Dashboard: Edit pane in edit mode (#96971)
* Dashboard: Edit pane foundations

* Update

* fix panel edit padding

* Restore scroll pos works when feature toggle is disabled

* Update

* Update

* remember collapsed state

* Update

* fixed padding issue
2024-11-26 14:39:09 +01:00

249 lines
7.4 KiB
TypeScript

import { css, cx } from '@emotion/css';
import { useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SceneComponentProps, VizPanel } from '@grafana/scenes';
import { Button, Spinner, ToolbarButton, useStyles2 } from '@grafana/ui';
import { useEditPaneCollapsed } from '../edit-pane/shared';
import { NavToolbarActions } from '../scene/NavToolbarActions';
import { UnlinkModal } from '../scene/UnlinkModal';
import { getDashboardSceneFor, getLibraryPanelBehavior } from '../utils/utils';
import { PanelEditor } from './PanelEditor';
import { SaveLibraryVizPanelModal } from './SaveLibraryVizPanelModal';
import { useSnappingSplitter } from './splitter/useSnappingSplitter';
export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>) {
const dashboard = getDashboardSceneFor(model);
const { optionsPane } = model.useState();
const styles = useStyles2(getStyles);
const [isCollapsed, setIsCollapsed] = useEditPaneCollapsed();
const { containerProps, primaryProps, secondaryProps, splitterProps, splitterState, onToggleCollapse } =
useSnappingSplitter({
direction: 'row',
dragPosition: 'end',
initialSize: 0.8,
collapsed: isCollapsed,
paneOptions: {
collapseBelowPixels: 250,
snapOpenToPixels: 400,
},
});
useEffect(() => {
setIsCollapsed(splitterState.collapsed);
}, [splitterState.collapsed, setIsCollapsed]);
return (
<>
<NavToolbarActions dashboard={dashboard} />
<div
{...containerProps}
className={cx(containerProps.className, styles.content)}
data-testid={selectors.components.PanelEditor.General.content}
>
<div {...primaryProps} className={cx(primaryProps.className, styles.body)}>
<VizAndDataPane model={model} />
</div>
<div {...splitterProps} />
<div {...secondaryProps} className={cx(secondaryProps.className, styles.optionsPane)}>
{splitterState.collapsed && (
<div className={styles.expandOptionsWrapper}>
<ToolbarButton
tooltip={'Open options pane'}
icon={'arrow-to-right'}
onClick={onToggleCollapse}
variant="canvas"
className={styles.rotate180}
aria-label={'Open options pane'}
/>
</div>
)}
{!splitterState.collapsed && optionsPane && <optionsPane.Component model={optionsPane} />}
{!splitterState.collapsed && !optionsPane && <Spinner />}
</div>
</div>
</>
);
}
function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
const dashboard = getDashboardSceneFor(model);
const { dataPane, showLibraryPanelSaveModal, showLibraryPanelUnlinkModal, tableView } = model.useState();
const panel = model.getPanel();
const libraryPanel = getLibraryPanelBehavior(panel);
const { controls } = dashboard.useState();
const styles = useStyles2(getStyles);
const { containerProps, primaryProps, secondaryProps, splitterProps, splitterState, onToggleCollapse } =
useSnappingSplitter({
direction: 'column',
dragPosition: 'start',
initialSize: 0.5,
paneOptions: {
collapseBelowPixels: 150,
},
});
containerProps.className = cx(containerProps.className, styles.container);
if (!dataPane) {
primaryProps.style.flexGrow = 1;
}
return (
<div className={cx(styles.pageContainer, controls && styles.pageContainerWithControls)}>
{controls && (
<div className={styles.controlsWrapper}>
<controls.Component model={controls} />
</div>
)}
<div {...containerProps}>
<div {...primaryProps}>
<VizWrapper panel={panel} tableView={tableView} />
</div>
{showLibraryPanelSaveModal && libraryPanel && (
<SaveLibraryVizPanelModal
libraryPanel={libraryPanel}
onDismiss={model.onDismissLibraryPanelSaveModal}
onConfirm={model.onConfirmSaveLibraryPanel}
onDiscard={model.onDiscard}
></SaveLibraryVizPanelModal>
)}
{showLibraryPanelUnlinkModal && libraryPanel && (
<UnlinkModal
onDismiss={model.onDismissUnlinkLibraryPanelModal}
onConfirm={model.onConfirmUnlinkLibraryPanel}
isOpen
/>
)}
{dataPane && (
<>
<div {...splitterProps} />
<div {...secondaryProps}>
{splitterState.collapsed && (
<div className={styles.expandDataPane}>
<Button
tooltip={'Open query pane'}
icon={'arrow-to-right'}
onClick={onToggleCollapse}
variant="secondary"
size="sm"
className={styles.openDataPaneButton}
aria-label={'Open query pane'}
/>
</div>
)}
{!splitterState.collapsed && <dataPane.Component model={dataPane} />}
</div>
</>
)}
</div>
</div>
);
}
interface VizWrapperProps {
panel: VizPanel;
tableView?: VizPanel;
}
function VizWrapper({ panel, tableView }: VizWrapperProps) {
const styles = useStyles2(getStyles);
const panelToShow = tableView ?? panel;
return (
<div className={styles.vizWrapper}>
<panelToShow.Component model={panelToShow} />
</div>
);
}
function getStyles(theme: GrafanaTheme2) {
return {
pageContainer: css({
display: 'grid',
gridTemplateAreas: `
"panels"`,
gridTemplateColumns: `1fr`,
gridTemplateRows: '1fr',
height: '100%',
}),
pageContainerWithControls: css({
gridTemplateAreas: `
"controls"
"panels"`,
gridTemplateRows: 'auto 1fr',
}),
container: css({
gridArea: 'panels',
height: '100%',
}),
canvasContent: css({
label: 'canvas-content',
display: 'flex',
flexDirection: 'column',
flexBasis: '100%',
flexGrow: 1,
minHeight: 0,
width: '100%',
}),
content: css({
position: 'absolute',
width: '100%',
height: '100%',
}),
body: css({
label: 'body',
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
minHeight: 0,
}),
optionsPane: css({
flexDirection: 'column',
borderLeft: `1px solid ${theme.colors.border.weak}`,
background: theme.colors.background.primary,
}),
expandOptionsWrapper: css({
display: 'flex',
flexDirection: 'column',
padding: theme.spacing(2, 1),
}),
expandDataPane: css({
display: 'flex',
flexDirection: 'row',
padding: theme.spacing(1),
borderTop: `1px solid ${theme.colors.border.weak}`,
borderRight: `1px solid ${theme.colors.border.weak}`,
background: theme.colors.background.primary,
flexGrow: 1,
justifyContent: 'space-around',
}),
rotate180: css({
rotate: '180deg',
}),
controlsWrapper: css({
display: 'flex',
flexDirection: 'column',
flexGrow: 0,
gridArea: 'controls',
}),
openDataPaneButton: css({
width: theme.spacing(8),
justifyContent: 'center',
svg: {
rotate: '-90deg',
},
}),
vizWrapper: css({
height: '100%',
width: '100%',
paddingLeft: theme.spacing(2),
}),
};
}