mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Canvas: Quick positioning (#48992)
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
parent
d7c65d3323
commit
b1ba0bc7b0
@ -94,6 +94,9 @@ import u1067 from '!!raw-loader!../../../../../public/img/icons/unicons/forward.
|
||||
import u1068 from '!!raw-loader!../../../../../public/img/icons/unicons/graph-bar.svg';
|
||||
import u1069 from '!!raw-loader!../../../../../public/img/icons/unicons/history.svg';
|
||||
import u1070 from '!!raw-loader!../../../../../public/img/icons/unicons/home-alt.svg';
|
||||
import u1158 from '!!raw-loader!../../../../../public/img/icons/unicons/horizontal-align-center.svg';
|
||||
import u1156 from '!!raw-loader!../../../../../public/img/icons/unicons/horizontal-align-left.svg';
|
||||
import u1157 from '!!raw-loader!../../../../../public/img/icons/unicons/horizontal-align-right.svg';
|
||||
import u1126 from '!!raw-loader!../../../../../public/img/icons/unicons/hourglass.svg';
|
||||
import u1071 from '!!raw-loader!../../../../../public/img/icons/unicons/import.svg';
|
||||
import u1073 from '!!raw-loader!../../../../../public/img/icons/unicons/info-circle.svg';
|
||||
@ -154,6 +157,9 @@ import u1117 from '!!raw-loader!../../../../../public/img/icons/unicons/unlock.s
|
||||
import u1118 from '!!raw-loader!../../../../../public/img/icons/unicons/upload.svg';
|
||||
import u1119 from '!!raw-loader!../../../../../public/img/icons/unicons/user.svg';
|
||||
import u1120 from '!!raw-loader!../../../../../public/img/icons/unicons/users-alt.svg';
|
||||
import u1160 from '!!raw-loader!../../../../../public/img/icons/unicons/vertical-align-bottom.svg';
|
||||
import u1161 from '!!raw-loader!../../../../../public/img/icons/unicons/vertical-align-center.svg';
|
||||
import u1159 from '!!raw-loader!../../../../../public/img/icons/unicons/vertical-align-top.svg';
|
||||
import u1121 from '!!raw-loader!../../../../../public/img/icons/unicons/wrap-text.svg';
|
||||
import u1135 from '!!raw-loader!../../../../../public/img/icons/unicons/x.svg';
|
||||
import { cacheStore } from 'react-inlinesvg';
|
||||
@ -329,4 +335,10 @@ export function initIconCache() {
|
||||
cacheItem(u1153, 'mono/panel-add.svg');
|
||||
cacheItem(u1154, 'mono/library-panel.svg');
|
||||
cacheItem(u1155, 'unicons/record-audio.svg');
|
||||
cacheItem(u1156, 'unicons/horizontal-align-left.svg');
|
||||
cacheItem(u1157, 'unicons/horizontal-align-right.svg');
|
||||
cacheItem(u1158, 'unicons/horizontal-align-center.svg');
|
||||
cacheItem(u1159, 'unicons/vertical-align-top.svg');
|
||||
cacheItem(u1160, 'unicons/vertical-align-bottom.svg');
|
||||
cacheItem(u1161, 'unicons/vertical-align-center.svg');
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Field, FieldType } from '@grafana/data';
|
||||
|
||||
import { ComponentSize } from './size';
|
||||
|
||||
export type IconType = 'mono' | 'default' | 'solid';
|
||||
export type IconSize = ComponentSize | 'xl' | 'xxl' | 'xxxl';
|
||||
|
||||
@ -106,6 +107,9 @@ export const getAvailableIcons = () =>
|
||||
'heart-break',
|
||||
'history',
|
||||
'home-alt',
|
||||
'horizontal-align-center',
|
||||
'horizontal-align-left',
|
||||
'horizontal-align-right',
|
||||
'hourglass',
|
||||
'import',
|
||||
'info',
|
||||
@ -171,6 +175,9 @@ export const getAvailableIcons = () =>
|
||||
'upload',
|
||||
'user',
|
||||
'users-alt',
|
||||
'vertical-align-bottom',
|
||||
'vertical-align-center',
|
||||
'vertical-align-top',
|
||||
'wrap-text',
|
||||
'x',
|
||||
] as const;
|
||||
|
@ -49,3 +49,12 @@ export interface LineConfig {
|
||||
color?: ColorDimensionConfig;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export enum QuickPlacement {
|
||||
Top = 'top',
|
||||
Bottom = 'bottom',
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
HorizontalCenter = 'hcenter',
|
||||
VerticalCenter = 'vcenter',
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { NumberInput } from 'app/features/dimensions/editors/NumberInput';
|
||||
import { PanelOptions } from '../models.gen';
|
||||
|
||||
import { ConstraintSelectionBox } from './ConstraintSelectionBox';
|
||||
import { QuickPositioning } from './QuickPositioning';
|
||||
import { CanvasEditorOptions } from './elementEditor';
|
||||
|
||||
const places: Array<keyof Placement> = ['top', 'left', 'bottom', 'right', 'width', 'height'];
|
||||
@ -72,26 +73,35 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
|
||||
const onPositionChange = (value: number | undefined, placement: keyof Placement) => {
|
||||
element.options.placement![placement] = value ?? element.options.placement![placement];
|
||||
element.applyLayoutStylesToDiv();
|
||||
settings.scene.clearCurrentSelection();
|
||||
settings.scene.clearCurrentSelection(true);
|
||||
// TODO: This needs to have a better sync method with where div is
|
||||
setTimeout(() => {
|
||||
settings.scene.select({ targets: [element.div!] });
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HorizontalGroup>
|
||||
<ConstraintSelectionBox
|
||||
onVerticalConstraintChange={onVerticalConstraintChange}
|
||||
onHorizontalConstraintChange={onHorizontalConstraintChange}
|
||||
currentConstraints={element.options.constraint ?? {}}
|
||||
/>
|
||||
<VerticalGroup>
|
||||
<Select options={verticalOptions} onChange={onVerticalConstraintSelect} value={layout?.vertical} />
|
||||
<Select
|
||||
options={horizontalOptions}
|
||||
onChange={onHorizontalConstraintSelect}
|
||||
value={options.constraint?.horizontal}
|
||||
<QuickPositioning onPositionChange={onPositionChange} settings={settings} element={element} />
|
||||
<br />
|
||||
<Field label="Constraints">
|
||||
<HorizontalGroup>
|
||||
<ConstraintSelectionBox
|
||||
onVerticalConstraintChange={onVerticalConstraintChange}
|
||||
onHorizontalConstraintChange={onHorizontalConstraintChange}
|
||||
currentConstraints={element.options.constraint ?? {}}
|
||||
/>
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
<VerticalGroup>
|
||||
<Select options={verticalOptions} onChange={onVerticalConstraintSelect} value={layout?.vertical} />
|
||||
<Select
|
||||
options={horizontalOptions}
|
||||
onChange={onHorizontalConstraintSelect}
|
||||
value={options.constraint?.horizontal}
|
||||
/>
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
</Field>
|
||||
|
||||
<br />
|
||||
|
||||
<Field label="Position">
|
||||
|
122
public/app/plugins/panel/canvas/editor/QuickPositioning.tsx
Normal file
122
public/app/plugins/panel/canvas/editor/QuickPositioning.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data/src';
|
||||
import { IconButton, useStyles2 } from '@grafana/ui/src';
|
||||
import { HorizontalConstraint, Placement, QuickPlacement, VerticalConstraint } from 'app/features/canvas';
|
||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||
|
||||
import { CanvasEditorOptions } from './elementEditor';
|
||||
|
||||
type Props = {
|
||||
onPositionChange: (value: number | undefined, placement: keyof Placement) => void;
|
||||
element: ElementState;
|
||||
settings: CanvasEditorOptions;
|
||||
};
|
||||
|
||||
export const QuickPositioning = ({ onPositionChange, element, settings }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onQuickPositioningChange = (position: QuickPlacement) => {
|
||||
const defaultConstraint = { vertical: VerticalConstraint.Top, horizontal: HorizontalConstraint.Left };
|
||||
const originalConstraint = { ...element.options.constraint };
|
||||
|
||||
element.options.constraint = defaultConstraint;
|
||||
element.setPlacementFromConstraint();
|
||||
|
||||
switch (position) {
|
||||
case QuickPlacement.Top:
|
||||
onPositionChange(0, 'top');
|
||||
break;
|
||||
case QuickPlacement.Bottom:
|
||||
onPositionChange(getRightBottomPosition(element.options.placement?.height ?? 0, 'bottom'), 'top');
|
||||
break;
|
||||
case QuickPlacement.VerticalCenter:
|
||||
onPositionChange(getCenterPosition(element.options.placement?.height ?? 0, 'v'), 'top');
|
||||
break;
|
||||
case QuickPlacement.Left:
|
||||
onPositionChange(0, 'left');
|
||||
break;
|
||||
case QuickPlacement.Right:
|
||||
onPositionChange(getRightBottomPosition(element.options.placement?.width ?? 0, 'right'), 'left');
|
||||
break;
|
||||
case QuickPlacement.HorizontalCenter:
|
||||
onPositionChange(getCenterPosition(element.options.placement?.width ?? 0, 'h'), 'left');
|
||||
break;
|
||||
}
|
||||
|
||||
element.options.constraint = originalConstraint;
|
||||
element.setPlacementFromConstraint();
|
||||
};
|
||||
|
||||
// Basing this on scene will mean that center is based on root for the time being
|
||||
const getCenterPosition = (elementSize: number, align: 'h' | 'v') => {
|
||||
const sceneSize = align === 'h' ? settings.scene.width : settings.scene.height;
|
||||
|
||||
return (sceneSize - elementSize) / 2;
|
||||
};
|
||||
|
||||
const getRightBottomPosition = (elementSize: number, align: 'right' | 'bottom') => {
|
||||
const sceneSize = align === 'right' ? settings.scene.width : settings.scene.height;
|
||||
|
||||
return sceneSize - elementSize;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.buttonGroup}>
|
||||
<IconButton
|
||||
name={'horizontal-align-left'}
|
||||
onClick={() => onQuickPositioningChange(QuickPlacement.Left)}
|
||||
className={styles.button}
|
||||
size={'lg'}
|
||||
tooltip={'Align left'}
|
||||
/>
|
||||
<IconButton
|
||||
name={'horizontal-align-center'}
|
||||
onClick={() => onQuickPositioningChange(QuickPlacement.HorizontalCenter)}
|
||||
className={styles.button}
|
||||
size={'lg'}
|
||||
tooltip={'Align horizontal centers'}
|
||||
/>
|
||||
<IconButton
|
||||
name={'horizontal-align-right'}
|
||||
onClick={() => onQuickPositioningChange(QuickPlacement.Right)}
|
||||
className={styles.button}
|
||||
size={'lg'}
|
||||
tooltip={'Align right'}
|
||||
/>
|
||||
<IconButton
|
||||
name={'vertical-align-top'}
|
||||
onClick={() => onQuickPositioningChange(QuickPlacement.Top)}
|
||||
size={'lg'}
|
||||
tooltip={'Align top'}
|
||||
/>
|
||||
<IconButton
|
||||
name={'vertical-align-center'}
|
||||
onClick={() => onQuickPositioningChange(QuickPlacement.VerticalCenter)}
|
||||
className={styles.button}
|
||||
size={'lg'}
|
||||
tooltip={'Align vertical centers'}
|
||||
/>
|
||||
<IconButton
|
||||
name={'vertical-align-bottom'}
|
||||
onClick={() => onQuickPositioningChange(QuickPlacement.Bottom)}
|
||||
className={styles.button}
|
||||
size={'lg'}
|
||||
tooltip={'Align bottom'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
buttonGroup: css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 0 12px 0;
|
||||
`,
|
||||
button: css`
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
`,
|
||||
});
|
@ -86,7 +86,7 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<
|
||||
category: ['Layout'],
|
||||
id: 'content',
|
||||
path: '__', // not used
|
||||
name: 'Constraints',
|
||||
name: 'Quick placement',
|
||||
editor: PlacementEditor,
|
||||
settings: opts,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user