Canvas: Quick positioning (#48992)

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Adela Almasan 2022-05-23 17:31:39 -05:00 committed by GitHub
parent d7c65d3323
commit b1ba0bc7b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 176 additions and 16 deletions

View File

@ -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');
}

View File

@ -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;

View File

@ -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',
}

View File

@ -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">

View 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;
`,
});

View File

@ -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,
});