mirror of
https://github.com/grafana/grafana.git
synced 2025-12-21 12:04:45 +08:00
Compare commits
11 Commits
data-manip
...
data-manip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebf77ddd9f | ||
|
|
de5d276848 | ||
|
|
57168535ef | ||
|
|
5977350c51 | ||
|
|
d965857ff0 | ||
|
|
444eabcabb | ||
|
|
f55e17f853 | ||
|
|
18f99ceaa9 | ||
|
|
d2f44c3859 | ||
|
|
21e82214a6 | ||
|
|
c2b8e7fcd5 |
@@ -41,6 +41,7 @@ export const availableIconsIndex = {
|
||||
asserts: true,
|
||||
'expand-arrows': true,
|
||||
'expand-arrows-alt': true,
|
||||
'expand-alt': true,
|
||||
at: true,
|
||||
ai: true,
|
||||
'ai-pointer': true,
|
||||
@@ -87,6 +88,7 @@ export const availableIconsIndex = {
|
||||
'comments-alt': true,
|
||||
compass: true,
|
||||
'compress-arrows': true,
|
||||
'compress-alt': true,
|
||||
copy: true,
|
||||
'corner-up-left': true,
|
||||
'corner-up-right': true,
|
||||
@@ -97,6 +99,7 @@ export const availableIconsIndex = {
|
||||
cube: true,
|
||||
dashboard: true,
|
||||
database: true,
|
||||
'debug-handle': true,
|
||||
'dice-three': true,
|
||||
docker: true,
|
||||
'document-info': true,
|
||||
|
||||
@@ -71,6 +71,7 @@ export const getDragStyles = (theme: GrafanaTheme2, handlePosition?: DragHandleP
|
||||
|
||||
const beforeHorizontal = {
|
||||
borderTop: '1px solid transparent',
|
||||
width: '100%',
|
||||
top: horizontalOffset,
|
||||
transform: 'translateY(-50%)',
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"unicons/comment-alt-share",
|
||||
"unicons/comments-alt",
|
||||
"unicons/compass",
|
||||
"unicons/compress-alt",
|
||||
"unicons/copy",
|
||||
"unicons/corner-down-right-alt",
|
||||
"unicons/corner-up-left",
|
||||
@@ -63,6 +64,7 @@
|
||||
"unicons/cube",
|
||||
"unicons/dashboard",
|
||||
"unicons/database",
|
||||
"unicons/debug-handle",
|
||||
"unicons/document-info",
|
||||
"unicons/download-alt",
|
||||
"unicons/draggabledots",
|
||||
@@ -73,6 +75,7 @@
|
||||
"unicons/exchange-alt",
|
||||
"unicons/exclamation-circle",
|
||||
"unicons/exclamation-triangle",
|
||||
"unicons/expand-alt",
|
||||
"unicons/external-link-alt",
|
||||
"unicons/eye",
|
||||
"unicons/eye-slash",
|
||||
|
||||
@@ -135,7 +135,7 @@ export const AddDataItemMenu = memo(
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
textButton: css({
|
||||
paddingLeft: 0,
|
||||
paddingLeft: theme.spacing(1),
|
||||
fontFamily: theme.typography.fontFamilyMonospace,
|
||||
}),
|
||||
queryLibraryMenuItem: css({
|
||||
|
||||
@@ -102,6 +102,7 @@ export function PanelDataSidebar({
|
||||
return selectedQueryTransform;
|
||||
}, [transformPickerIndex, selectedQueryTransform, allItems]);
|
||||
const selectedItem = useMemo(() => allItems.find((item) => item.id === selectedId), [allItems, selectedId]);
|
||||
const shouldShowAlerting = useMemo(() => tabs.some((tab) => tab.tabId === TabId.Alert), [tabs]);
|
||||
|
||||
const updateQuerySelectionOnStateChange = useCallback(
|
||||
(index: number) => {
|
||||
@@ -329,6 +330,7 @@ export function PanelDataSidebar({
|
||||
transformItems={transformItems}
|
||||
selectedId={selectedId}
|
||||
sidebarSize={sidebarState.size}
|
||||
hasAlerting={shouldShowAlerting}
|
||||
onResizeSidebar={(size: SidebarSize) => setSidebarState((prevState) => ({ ...prevState, size }))}
|
||||
onCollapseSidebar={() => setSidebarState((prevState) => ({ ...prevState, collapsed: true }))}
|
||||
onSelect={(id) => {
|
||||
|
||||
@@ -313,7 +313,7 @@ const getStyles = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataP
|
||||
},
|
||||
}),
|
||||
content: css({
|
||||
padding: theme.spacing(1.5),
|
||||
padding: theme.spacing(0.75) + ' ' + theme.spacing(1),
|
||||
}),
|
||||
datasourceIcon: css({
|
||||
width: '16px',
|
||||
|
||||
@@ -32,10 +32,12 @@ import {
|
||||
} from '@grafana/ui';
|
||||
import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
import { getExpressionIcon } from 'app/features/expressions/types';
|
||||
import { FALLBACK_DOCS_LINK } from 'app/features/transformers/docs/constants';
|
||||
|
||||
import { getQueryRunnerFor } from '../../utils/utils';
|
||||
|
||||
import { usePanelDataPaneColors } from './theme';
|
||||
import { QueryTransformItem } from './types';
|
||||
|
||||
// Props for regular item mode
|
||||
@@ -69,21 +71,26 @@ interface QueryLibraryModeProps {
|
||||
|
||||
type QueryTransformDetailViewHeaderProps = ItemModeProps | QueryLibraryModeProps;
|
||||
|
||||
const ITEM_CONFIG = (theme: GrafanaTheme2) => ({
|
||||
const ITEM_CONFIG = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataPaneColors>) => ({
|
||||
query: {
|
||||
color: theme.colors.primary.main,
|
||||
color: colors.query.accent,
|
||||
icon: 'database' as const,
|
||||
},
|
||||
expression: {
|
||||
color: theme.visualization.getColorByName('purple'),
|
||||
icon: 'calculator-alt' as const,
|
||||
color: colors.expression.accent,
|
||||
icon: (item: QueryTransformItem) => {
|
||||
if (item.type === 'expression') {
|
||||
return getExpressionIcon(item.data.type);
|
||||
}
|
||||
return 'calculator-alt';
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
color: theme.visualization.getColorByName('orange'),
|
||||
icon: 'process' as const,
|
||||
color: colors.transform.accent,
|
||||
icon: 'pivot' as const,
|
||||
},
|
||||
queryLibrary: {
|
||||
color: theme.visualization.getColorByName('green'),
|
||||
color: colors.query.accent,
|
||||
icon: 'bookmark' as const,
|
||||
},
|
||||
});
|
||||
@@ -101,7 +108,8 @@ function QueryLibraryHeader({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const theme = useTheme2();
|
||||
const config = useMemo(() => ITEM_CONFIG(theme).queryLibrary, [theme]);
|
||||
const colors = usePanelDataPaneColors();
|
||||
const config = useMemo(() => ITEM_CONFIG(theme, colors).queryLibrary, [theme, colors]);
|
||||
const styles = useStyles2(getStyles, config);
|
||||
|
||||
return (
|
||||
@@ -159,7 +167,8 @@ function ItemHeader({
|
||||
debugPosition,
|
||||
}: ItemModeProps) {
|
||||
const theme = useTheme2();
|
||||
const config = useMemo(() => ITEM_CONFIG(theme)[selectedItem.type], [theme, selectedItem.type]);
|
||||
const colors = usePanelDataPaneColors();
|
||||
const config = useMemo(() => ITEM_CONFIG(theme, colors)[selectedItem.type], [theme, selectedItem.type, colors]);
|
||||
const styles = useStyles2(getStyles, config);
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
@@ -471,7 +480,10 @@ function ItemHeader({
|
||||
<div className={styles.headerContent}>
|
||||
{/* Left side: Icon, Datasource, Name */}
|
||||
<Stack gap={1} alignItems="center" grow={1} minWidth={0}>
|
||||
<Icon name={config.icon} className={styles.icon} />
|
||||
<Icon
|
||||
name={typeof config.icon === 'function' ? config.icon(selectedItem) : config.icon}
|
||||
className={styles.icon}
|
||||
/>
|
||||
|
||||
{/* Datasource picker for queries */}
|
||||
{selectedItem.type === 'query' && datasourceSettings && (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd';
|
||||
import { HTMLAttributes, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Fragment, HTMLAttributes, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { Button, Icon, ScrollContainer, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { Button, Icon, IconButton, ScrollContainer, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { ExpressionQueryType } from 'app/features/expressions/types';
|
||||
|
||||
import { AddDataItemMenu } from './AddDataItemMenu';
|
||||
@@ -25,6 +25,7 @@ interface QueryTransformListProps {
|
||||
sidebarSize: SidebarSize;
|
||||
transformItems: TransformItem[];
|
||||
selectedId: string | null;
|
||||
hasAlerting?: boolean;
|
||||
onSelect: (id: string) => void;
|
||||
onAddQuery: (index?: number) => void;
|
||||
onAddFromSavedQueries: (index?: number) => void;
|
||||
@@ -50,6 +51,7 @@ export const QueryTransformList = memo(
|
||||
allItems,
|
||||
selectedId,
|
||||
sidebarSize,
|
||||
hasAlerting,
|
||||
onSelect,
|
||||
onAddQuery,
|
||||
onAddFromSavedQueries,
|
||||
@@ -357,17 +359,30 @@ export const QueryTransformList = memo(
|
||||
<div className={styles.container} onMouseLeave={() => setHovered(null)}>
|
||||
<div className={styles.header}>
|
||||
<Stack justifyContent="space-between" alignItems="center" gap={2}>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
className={styles.headerTitle}
|
||||
onClick={() => onResizeSidebar(sidebarSize === SidebarSize.Mini ? SidebarSize.Full : SidebarSize.Mini)}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<Icon name={sidebarSize === SidebarSize.Mini ? 'expand-arrows' : 'compress-arrows'} />
|
||||
<span>{t('dashboard-scene.query-transform-list.header', 'Pipeline flow')}</span>
|
||||
</Stack>
|
||||
</div>
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<IconButton
|
||||
tooltip={
|
||||
sidebarSize === SidebarSize.Mini
|
||||
? t('dashboard-scene.query-transform-list.expand-sidebar', 'Expand sidebar')
|
||||
: t('dashboard-scene.query-transform-list.collapse-sidebar', 'Collapse sidebar')
|
||||
}
|
||||
onClick={() => onResizeSidebar(sidebarSize === SidebarSize.Mini ? SidebarSize.Full : SidebarSize.Mini)}
|
||||
className={styles.rotate90}
|
||||
name={sidebarSize === SidebarSize.Mini ? 'expand-alt' : 'compress-alt'}
|
||||
/>
|
||||
<span className={styles.headerTitle}>{t('dashboard-scene.query-transform-list.header', 'Pipeline')}</span>
|
||||
</Stack>
|
||||
<Stack direction="row" gap={0.5}>
|
||||
{hasAlerting && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
size="sm"
|
||||
tooltip={t('dashboard-scene.query-transform-list.notifications-tooltip', 'Alerts (coming soon)')}
|
||||
>
|
||||
<Icon name="bell" size="sm" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
@@ -449,7 +464,7 @@ export const QueryTransformList = memo(
|
||||
)}
|
||||
data-testid="query-transform-list-content"
|
||||
>
|
||||
<Stack direction="column" gap={3}>
|
||||
<Stack direction="column" gap={2}>
|
||||
{/* Data Sources Section (Queries + Expressions) */}
|
||||
<Stack direction="column" gap={2}>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
@@ -485,9 +500,8 @@ export const QueryTransformList = memo(
|
||||
const showDebugLineAfter = isDebugMode && globalIndex === debugPosition - 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Fragment key={item.id}>
|
||||
<div
|
||||
key={item.id}
|
||||
className={cx(styles.cardContainer, {
|
||||
[styles.cardDebugDisabled]: isDebugDisabled,
|
||||
})}
|
||||
@@ -565,11 +579,11 @@ export const QueryTransformList = memo(
|
||||
}}
|
||||
>
|
||||
<div className={styles.debugLineHandle}>
|
||||
<Icon name="draggabledots" size="sm" />
|
||||
<Icon name="debug-handle" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{provided.placeholder}
|
||||
@@ -640,9 +654,8 @@ export const QueryTransformList = memo(
|
||||
const showDebugLineAfter = isDebugMode && globalIndex === debugPosition - 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Fragment key={item.id}>
|
||||
<div
|
||||
key={item.id}
|
||||
className={cx(styles.cardContainer, {
|
||||
[styles.cardDebugDisabled]: isDebugDisabled,
|
||||
})}
|
||||
@@ -721,11 +734,11 @@ export const QueryTransformList = memo(
|
||||
}}
|
||||
>
|
||||
<div className={styles.debugLineHandle}>
|
||||
<Icon name="draggabledots" size="sm" />
|
||||
<Icon name="debug-handle" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{provided.placeholder}
|
||||
@@ -805,8 +818,6 @@ const getStyles = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataP
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderLeft: 'none',
|
||||
}),
|
||||
header: css({
|
||||
...barBase,
|
||||
@@ -821,7 +832,6 @@ const getStyles = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataP
|
||||
fontFamily: theme.typography.fontFamilyMonospace,
|
||||
textTransform: 'uppercase',
|
||||
color: theme.colors.text.primary,
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
sectionLabel: css({
|
||||
cursor: 'pointer',
|
||||
@@ -1018,7 +1028,8 @@ const getStyles = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataP
|
||||
letterSpacing: '0.05em',
|
||||
}),
|
||||
debugButtonActive: css({
|
||||
'&:focus, &:active, &:focus:active': {
|
||||
background: '#441306',
|
||||
'&:hover, &:focus, &:active, &:focus:active': {
|
||||
background: '#441306',
|
||||
},
|
||||
}),
|
||||
@@ -1050,7 +1061,8 @@ const getStyles = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataP
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
background: colors.query.accent,
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
borderRadius: theme.shape.radius.default,
|
||||
height: theme.spacing(2),
|
||||
padding: theme.spacing(0.5),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -1061,5 +1073,8 @@ const getStyles = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataP
|
||||
cursor: 'grabbing',
|
||||
},
|
||||
}),
|
||||
rotate90: css({
|
||||
transform: 'rotate(90deg)',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import { getDashboardSceneFor, getLibraryPanelBehavior } from '../utils/utils';
|
||||
import { PanelDataSidebar, SidebarSize, SidebarState } from './PanelDataPane/PanelDataSidebar';
|
||||
import { PanelEditor } from './PanelEditor';
|
||||
import { SaveLibraryVizPanelModal } from './SaveLibraryVizPanelModal';
|
||||
import { useHorizontalResize, useVerticalResize } from './hooks';
|
||||
import { useSnappingSplitter } from './splitter/useSnappingSplitter';
|
||||
import { scrollReflowMediaCondition, useScrollReflowLimit } from './useScrollReflowLimit';
|
||||
|
||||
@@ -25,6 +26,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
const { optionsPane } = model.useState();
|
||||
const styles = useStyles2(getWrapperStyles);
|
||||
const [isInitiallyCollapsed, setIsCollapsed] = useEditPaneCollapsed();
|
||||
const [containerRef, { height: containerHeight }] = useMeasure<HTMLDivElement>();
|
||||
|
||||
const isScrollingLayout = useScrollReflowLimit();
|
||||
|
||||
@@ -46,7 +48,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
}, [splitterState.collapsed, setIsCollapsed]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: '100%' }} ref={containerRef}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div
|
||||
{...containerProps}
|
||||
@@ -54,7 +56,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
data-testid={selectors.components.PanelEditor.General.content}
|
||||
>
|
||||
<div {...primaryProps} className={cx(primaryProps.className, styles.body)}>
|
||||
<VizAndDataPane model={model} />
|
||||
<VizAndDataPane model={model} containerHeight={Math.max(containerHeight, 500)} />
|
||||
</div>
|
||||
<div {...splitterProps} />
|
||||
<div {...secondaryProps} className={cx(secondaryProps.className, styles.optionsPane)}>
|
||||
@@ -77,23 +79,45 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
{!splitterState.collapsed && !optionsPane && <Spinner />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
||||
function VizAndDataPane({
|
||||
model,
|
||||
containerHeight = 800,
|
||||
}: SceneComponentProps<PanelEditor> & { containerHeight?: number }) {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { dataPane, showLibraryPanelSaveModal, showLibraryPanelUnlinkModal, tableView } = model.useState();
|
||||
const panel = model.getPanel();
|
||||
const libraryPanel = getLibraryPanelBehavior(panel);
|
||||
const { controls } = dashboard.useState();
|
||||
const [sidebarState, setSidebarState] = useState<SidebarState>({ size: SidebarSize.Mini, collapsed: false });
|
||||
const [vizRef, { height: vizHeight }] = useMeasure<HTMLDivElement>();
|
||||
|
||||
const styles = useStyles2(getStyles, sidebarState);
|
||||
|
||||
const isScrollingLayout = useScrollReflowLimit();
|
||||
|
||||
// drag-resize hooks
|
||||
const {
|
||||
handleRef: sidebarHandleRef,
|
||||
width: sidebarWidth,
|
||||
className: sidebarResizerClassName,
|
||||
} = useHorizontalResize({
|
||||
initialWidth: 285,
|
||||
minWidth: 285,
|
||||
maxWidth: 380,
|
||||
});
|
||||
const {
|
||||
handleRef: vizHandleRef,
|
||||
height: vizHeight,
|
||||
className: vizResizerClassName,
|
||||
} = useVerticalResize({
|
||||
initialHeight: Math.max(containerHeight / 2, 200),
|
||||
minHeight: 200,
|
||||
maxHeight: containerHeight - 80,
|
||||
});
|
||||
|
||||
const gridStyles = useMemo(() => {
|
||||
const rows = [];
|
||||
const grid = [];
|
||||
@@ -107,7 +131,6 @@ function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
||||
rows.push(`${vizHeight}px`);
|
||||
|
||||
if (dataPane) {
|
||||
// rows.push(`${(containerHeight - vizHeight) + 40}px`);
|
||||
rows.push('auto');
|
||||
grid.push(['sidebar', 'data-pane']);
|
||||
if (sidebarState.size === SidebarSize.Full) {
|
||||
@@ -118,10 +141,19 @@ function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
||||
}
|
||||
|
||||
return {
|
||||
height: containerHeight,
|
||||
maxHeight: containerHeight,
|
||||
gridTemplateAreas: '\n' + grid.map((row) => `"${row.join(' ')}"`).join('\n'),
|
||||
gridTemplateRows: rows.map((r) => r).join(' '),
|
||||
};
|
||||
}, [controls, dataPane, sidebarState.size, vizHeight]);
|
||||
}, [controls, dataPane, sidebarState.size, vizHeight, containerHeight]);
|
||||
|
||||
const bottomPaneHeight = containerHeight - vizHeight - 80;
|
||||
const expandedSidebarHeight = containerHeight - 16;
|
||||
|
||||
if (!containerHeight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.pageContainer} style={gridStyles}>
|
||||
@@ -131,17 +163,46 @@ function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={vizRef} style={{ height: 550 }} className={cx(styles.viz, isScrollingLayout && styles.fixedSizeViz)}>
|
||||
<div
|
||||
className={cx(styles.viz, isScrollingLayout && styles.fixedSizeViz)}
|
||||
style={{ height: vizHeight, maxHeight: containerHeight - 80 }}
|
||||
>
|
||||
{tableView ? <tableView.Component model={tableView} /> : <panel.Component model={panel} />}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, width: '100%' }}>
|
||||
<div
|
||||
style={{ height: 2, width: '100%' }}
|
||||
ref={vizHandleRef}
|
||||
className={vizResizerClassName}
|
||||
data-testid="viz-resizer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{dataPane && (
|
||||
<>
|
||||
<div className={cx(styles.dataPane, isScrollingLayout && styles.fullSizeEditor)}>
|
||||
<div
|
||||
className={cx(styles.dataPane, isScrollingLayout && styles.fullSizeEditor)}
|
||||
style={{ height: bottomPaneHeight }}
|
||||
>
|
||||
<dataPane.Component model={dataPane} />
|
||||
</div>
|
||||
<div className={styles.sidebar}>
|
||||
|
||||
<div
|
||||
className={styles.sidebar}
|
||||
style={{
|
||||
height: sidebarState.size === SidebarSize.Mini ? bottomPaneHeight : expandedSidebarHeight,
|
||||
width: sidebarWidth,
|
||||
}}
|
||||
>
|
||||
<PanelDataSidebar model={dataPane} sidebarState={sidebarState} setSidebarState={setSidebarState} />
|
||||
<div style={{ position: 'absolute', top: 0, bottom: 0, right: 0, height: '100%' }}>
|
||||
<div
|
||||
style={{ height: '100%', width: 2 }}
|
||||
ref={sidebarHandleRef}
|
||||
className={sidebarResizerClassName}
|
||||
data-testid="sidebar-resizer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -215,9 +276,6 @@ function getStyles(theme: GrafanaTheme2, sidebarState: SidebarState) {
|
||||
display: 'grid',
|
||||
gap: theme.spacing(2),
|
||||
gridTemplateColumns: `auto 1fr`,
|
||||
height: '100%',
|
||||
minHeight: '100%',
|
||||
maxHeight: '100%',
|
||||
overflow: 'hidden',
|
||||
[scrollReflowMediaQuery]: {
|
||||
gridTemplateColumns: `100%`,
|
||||
@@ -225,27 +283,25 @@ function getStyles(theme: GrafanaTheme2, sidebarState: SidebarState) {
|
||||
}),
|
||||
sidebar: css({
|
||||
gridArea: 'sidebar',
|
||||
overflow: 'auto',
|
||||
resize: 'horizontal',
|
||||
minWidth: 285,
|
||||
maxWidth: 400,
|
||||
overflow: 'visible',
|
||||
position: 'relative',
|
||||
...(sidebarState.size === SidebarSize.Mini && {
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
}),
|
||||
viz: css({
|
||||
gridArea: 'viz',
|
||||
overflow: 'auto',
|
||||
resize: 'vertical',
|
||||
overflow: 'visible',
|
||||
height: '100%',
|
||||
minHeight: 200,
|
||||
maxHeight: 700, // FIXME: needs a dynamic height
|
||||
minHeight: 100,
|
||||
position: 'relative',
|
||||
...(sidebarState.size === SidebarSize.Mini && {
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
}),
|
||||
dataPane: css({
|
||||
gridArea: 'data-pane',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
controlsWrapper: css({
|
||||
gridArea: 'controls',
|
||||
@@ -255,6 +311,7 @@ function getStyles(theme: GrafanaTheme2, sidebarState: SidebarState) {
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
}),
|
||||
|
||||
openDataPaneButton: css({
|
||||
width: theme.spacing(8),
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -158,7 +158,7 @@ function PanelOptionsPaneComponent({ model }: SceneComponentProps<PanelOptionsPa
|
||||
}, [pluginId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.wrapper}>
|
||||
{!isVizPickerOpen && (
|
||||
<>
|
||||
<div className={styles.top}>
|
||||
@@ -235,12 +235,19 @@ function PanelOptionsPaneComponent({ model }: SceneComponentProps<PanelOptionsPa
|
||||
showBackButton={config.featureToggles.newVizSuggestions ? hasPickedViz || !isNewPanel : true}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
wrapper: css({
|
||||
display: 'contents',
|
||||
'& h6': {
|
||||
fontFamily: theme.typography.fontFamilyMonospace,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
}),
|
||||
top: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
|
||||
87
public/app/features/dashboard-scene/panel-edit/hooks.ts
Normal file
87
public/app/features/dashboard-scene/panel-edit/hooks.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { getDragStyles, useStyles2 } from '@grafana/ui';
|
||||
|
||||
type UseHorizontalResizeOptions = {
|
||||
initialWidth: number;
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
};
|
||||
|
||||
type UseVerticalResizeOptions = {
|
||||
initialHeight: number;
|
||||
minHeight?: number;
|
||||
maxHeight?: number;
|
||||
};
|
||||
|
||||
export function useHorizontalResize({ initialWidth, minWidth = 0, maxWidth = Infinity }: UseHorizontalResizeOptions) {
|
||||
const [width, setWidth] = useState<number>(initialWidth);
|
||||
const styles = useStyles2(getDragStyles, 'middle');
|
||||
|
||||
const handleRef = useCallback(
|
||||
(handle: HTMLElement | null) => {
|
||||
let startX = 0;
|
||||
let startWidth = 0;
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
const delta = startX - e.clientX; // dragging left increases width of right sidebar
|
||||
const newWidth = Math.min(maxWidth, Math.max(minWidth, startWidth - delta));
|
||||
setWidth(newWidth);
|
||||
};
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
};
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
startX = e.clientX;
|
||||
startWidth = width;
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
};
|
||||
|
||||
if (handle?.nodeType === Node.ELEMENT_NODE) {
|
||||
handle.addEventListener('mousedown', onMouseDown);
|
||||
}
|
||||
},
|
||||
[maxWidth, minWidth, width]
|
||||
);
|
||||
|
||||
return { handleRef, width, setWidth, className: styles.dragHandleVertical };
|
||||
}
|
||||
|
||||
export function useVerticalResize({ initialHeight, minHeight = 0, maxHeight = Infinity }: UseVerticalResizeOptions) {
|
||||
const [height, setHeight] = useState<number>(initialHeight);
|
||||
const styles = useStyles2(getDragStyles, 'middle');
|
||||
|
||||
const handleRef = useCallback(
|
||||
(handle: HTMLElement | null) => {
|
||||
let startY = 0;
|
||||
let startHeight = 0;
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
const delta = e.clientY - startY; // dragging down increases height
|
||||
const newHeight = Math.min(maxHeight, Math.max(minHeight, startHeight + delta));
|
||||
setHeight(newHeight);
|
||||
};
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
};
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
startY = e.clientY;
|
||||
startHeight = height;
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
};
|
||||
|
||||
if (handle?.nodeType === Node.ELEMENT_NODE) {
|
||||
handle.addEventListener('mousedown', onMouseDown);
|
||||
}
|
||||
},
|
||||
[maxHeight, minHeight, height]
|
||||
);
|
||||
|
||||
return { handleRef, height, setHeight, className: styles.dragHandleHorizontal };
|
||||
}
|
||||
3
public/img/icons/unicons/debug-handle.svg
Normal file
3
public/img/icons/unicons/debug-handle.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.8 1.66667H15.2C15.4122 1.66667 15.6157 1.57887 15.7657 1.42259C15.9157 1.26631 16 1.05435 16 0.833333C16 0.61232 15.9157 0.400358 15.7657 0.244078C15.6157 0.0877975 15.4122 0 15.2 0H0.8C0.587827 0 0.384344 0.0877975 0.234315 0.244078C0.0842854 0.400358 0 0.61232 0 0.833333C0 1.05435 0.0842854 1.26631 0.234315 1.42259C0.384344 1.57887 0.587827 1.66667 0.8 1.66667ZM15.2 3.33333H0.8C0.587827 3.33333 0.384344 3.42113 0.234315 3.57741C0.0842854 3.73369 0 3.94565 0 4.16667C0 4.38768 0.0842854 4.59964 0.234315 4.75592C0.384344 4.9122 0.587827 5 0.8 5H15.2C15.4122 5 15.6157 4.9122 15.7657 4.75592C15.9157 4.59964 16 4.38768 16 4.16667C16 3.94565 15.9157 3.73369 15.7657 3.57741C15.6157 3.42113 15.4122 3.33333 15.2 3.33333Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 837 B |
@@ -6005,6 +6005,7 @@
|
||||
"hide-response": "Hide response",
|
||||
"input-data": "Input data",
|
||||
"output-data": "Output data",
|
||||
"query-inspector": "Query inspector",
|
||||
"remove-query": "Remove",
|
||||
"remove-transform": "Remove",
|
||||
"run-query": "RUN QUERY",
|
||||
@@ -6325,8 +6326,9 @@
|
||||
"debug-mode-enter": "Step through your pipeline",
|
||||
"debug-mode-exit": "Exit debug mode",
|
||||
"debug-position": "Debug position",
|
||||
"header": "Pipeline flow",
|
||||
"header": "Pipeline",
|
||||
"nodes": "nodes",
|
||||
"notifications-tooltip": "Alerts (coming soon)",
|
||||
"queries-expressions": "Queries & Expressions",
|
||||
"transformations": "Transformations"
|
||||
},
|
||||
@@ -6631,10 +6633,6 @@
|
||||
"aria-label-change-visualization": "Change visualization",
|
||||
"text": "Change"
|
||||
},
|
||||
"viz-and-data-pane": {
|
||||
"aria-label-open-query-pane": "Open query pane",
|
||||
"tooltip-open-query-pane": "Open query pane"
|
||||
},
|
||||
"viz-panel-links-renderer": {
|
||||
"aria-label-panel-links": "Panel links"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user