47853 canvas constraint visualizations (#49206)

Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
This commit is contained in:
Nathan Marrs 2022-05-18 18:49:57 -07:00 committed by GitHub
parent 3b017e0fb1
commit 35ea67c210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 230 additions and 16 deletions

View File

@ -0,0 +1,200 @@
import { MoveableManagerInterface, Renderer } from 'moveable';
import { HorizontalConstraint, VerticalConstraint } from '../types';
import { Scene } from './scene';
export const dimensionViewable = {
name: 'dimensionViewable',
props: {},
events: {},
render(moveable: MoveableManagerInterface<any, any>, React: Renderer) {
const rect = moveable.getRect();
return (
<div
key={'dimension-viewable'}
className={'moveable-dimension'}
style={{
position: 'absolute',
left: `${rect.width / 2}px`,
top: `${rect.height + 20}px`,
background: '#4af',
borderRadius: '2px',
padding: '2px 4px',
color: 'white',
fontSize: '13px',
whiteSpace: 'nowrap',
fontWeight: 'bold',
willChange: 'transform',
transform: 'translate(-50%, 0px)',
zIndex: 100,
}}
>
{Math.round(rect.offsetWidth)} x {Math.round(rect.offsetHeight)}
</div>
);
},
};
export const constraintViewable = (scene: Scene) => ({
name: 'constraintViewable',
props: {},
events: {},
render(moveable: MoveableManagerInterface<any, any>, React: Renderer) {
const rect = moveable.getRect();
const targetElement = scene.findElementByTarget(moveable.state.target);
// If target is currently in motion or selection is more than 1 element don't display constraint visualizations
if (
targetElement?.isMoving ||
(scene.selecto?.getSelectedTargets() && scene.selecto?.getSelectedTargets().length > 1)
) {
return;
}
let verticalConstraintVisualization = null;
let horizontalConstraintVisualization = null;
const constraint = targetElement?.options.constraint ?? {};
const borderStyle = '1px dashed #4af';
const centerIndicatorLineOne = React.createElement('div', {
style: {
position: 'absolute',
left: `${rect.width / 2}px`,
top: `${rect.height / 2 - rect.height / 16}px`,
borderLeft: borderStyle,
height: `${rect.height / 8}px`,
transform: 'rotate(45deg)',
},
});
const centerIndicatorLineTwo = React.createElement('div', {
style: {
position: 'absolute',
left: `${rect.width / 2}px`,
top: `${rect.height / 2 - rect.height / 16}px`,
borderLeft: borderStyle,
height: `${rect.height / 8}px`,
transform: 'rotate(-45deg)',
},
});
const centerIndicator = React.createElement('div', {}, [centerIndicatorLineOne, centerIndicatorLineTwo]);
const verticalConstraintTop = React.createElement('div', {
style: {
position: 'absolute',
left: `${rect.width / 2}px`,
bottom: '0px',
borderLeft: borderStyle,
height: '100vh',
},
});
const verticalConstraintBottom = React.createElement('div', {
style: {
position: 'absolute',
left: `${rect.width / 2}px`,
top: `${rect.height}px`,
borderLeft: borderStyle,
height: '100vh',
},
});
const verticalConstraintTopBottom = React.createElement('div', {}, [
verticalConstraintTop,
verticalConstraintBottom,
]);
const verticalConstraintCenterLine = React.createElement('div', {
style: {
position: 'absolute',
left: `${rect.width / 2}px`,
top: `${rect.height / 4}px`,
borderLeft: borderStyle,
height: `${rect.height / 2}px`,
},
});
const verticalConstraintCenter = React.createElement('div', {}, [verticalConstraintCenterLine, centerIndicator]);
switch (constraint.vertical) {
case VerticalConstraint.Top:
verticalConstraintVisualization = verticalConstraintTop;
break;
case VerticalConstraint.Bottom:
verticalConstraintVisualization = verticalConstraintBottom;
break;
case VerticalConstraint.TopBottom:
verticalConstraintVisualization = verticalConstraintTopBottom;
break;
case VerticalConstraint.Center:
verticalConstraintVisualization = verticalConstraintCenter;
break;
}
const horizontalConstraintLeft = React.createElement('div', {
style: {
position: 'absolute',
right: '0px',
top: `${rect.height / 2}px`,
borderTop: borderStyle,
width: '100vw',
},
});
const horizontalConstraintRight = React.createElement('div', {
style: {
position: 'absolute',
left: `${rect.width}px`,
top: `${rect.height / 2}px`,
borderTop: borderStyle,
width: '100vw',
},
});
const horizontalConstraintLeftRight = React.createElement('div', {}, [
horizontalConstraintLeft,
horizontalConstraintRight,
]);
const horizontalConstraintCenterLine = React.createElement('div', {
style: {
position: 'absolute',
left: `${rect.width / 4}px`,
top: `${rect.height / 2}px`,
borderTop: borderStyle,
width: `${rect.width / 2}px`,
},
});
const horizontalConstraintCenter = React.createElement('div', {}, [
horizontalConstraintCenterLine,
centerIndicator,
]);
switch (constraint.horizontal) {
case HorizontalConstraint.Left:
horizontalConstraintVisualization = horizontalConstraintLeft;
break;
case HorizontalConstraint.Right:
horizontalConstraintVisualization = horizontalConstraintRight;
break;
case HorizontalConstraint.LeftRight:
horizontalConstraintVisualization = horizontalConstraintLeftRight;
break;
case HorizontalConstraint.Center:
horizontalConstraintVisualization = horizontalConstraintCenter;
break;
}
const constraintVisualization = React.createElement('div', {}, [
verticalConstraintVisualization,
horizontalConstraintVisualization,
]);
return constraintVisualization;
},
});

View File

@ -26,6 +26,9 @@ export class ElementState implements LayerElement {
sizeStyle: CSSProperties = {};
dataStyle: CSSProperties = {};
// Determine whether or not element is in motion or not (via moveable)
isMoving = false;
// Filled in by ref
div?: HTMLDivElement;

View File

@ -11,23 +11,24 @@ import { config } from 'app/core/config';
import { CanvasFrameOptions, DEFAULT_CANVAS_ELEMENT_CONFIG } from 'app/features/canvas';
import {
ColorDimensionConfig,
DimensionContext,
ResourceDimensionConfig,
ScalarDimensionConfig,
ScaleDimensionConfig,
TextDimensionConfig,
DimensionContext,
ScalarDimensionConfig,
} from 'app/features/dimensions';
import {
getColorDimensionFromData,
getScaleDimensionFromData,
getResourceDimensionFromData,
getTextDimensionFromData,
getScalarDimensionFromData,
getScaleDimensionFromData,
getTextDimensionFromData,
} from 'app/features/dimensions/utils';
import { LayerActionID } from 'app/plugins/panel/canvas/types';
import { Placement } from '../types';
import { constraintViewable, dimensionViewable } from './ables';
import { ElementState } from './element';
import { FrameState } from './frame';
import { RootElement } from './root';
@ -224,7 +225,7 @@ export class Scene {
}
};
private findElementByTarget = (target: HTMLElement | SVGElement): ElementState | undefined => {
findElementByTarget = (target: HTMLElement | SVGElement): ElementState | undefined => {
// We will probably want to add memoization to this as we are calling on drag / resize
const stack = [...this.root.elements];
@ -307,11 +308,22 @@ export class Scene {
this.moveable = new Moveable(this.div!, {
draggable: allowChanges,
resizable: allowChanges,
ables: [dimensionViewable, constraintViewable(this)],
props: {
dimensionViewable: allowChanges,
constraintViewable: allowChanges,
},
origin: false,
})
.on('clickGroup', (event) => {
this.selecto!.clickTarget(event.inputEvent, event.inputTarget);
})
.on('dragStart', (event) => {
const targetedElement = this.findElementByTarget(event.target);
if (targetedElement) {
targetedElement.isMoving = true;
}
})
.on('drag', (event) => {
const targetedElement = this.findElementByTarget(event.target);
targetedElement!.applyDrag(event);
@ -325,7 +337,8 @@ export class Scene {
.on('dragEnd', (event) => {
const targetedElement = this.findElementByTarget(event.target);
if (targetedElement) {
targetedElement?.setPlacementFromConstraint();
targetedElement.setPlacementFromConstraint();
targetedElement.isMoving = false;
}
this.moved.next(Date.now());
@ -389,10 +402,4 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
overflow: hidden;
position: relative;
`,
toolbar: css`
position: absolute;
bottom: 0;
margin: 10px;
`,
}));

View File

@ -147,22 +147,26 @@ const getStyles = (currentConstraints: Constraint) => (theme: GrafanaTheme2) =>
}
`,
topConstraint: css`
${currentConstraints.vertical === VerticalConstraint.Top
${currentConstraints.vertical === VerticalConstraint.Top ||
currentConstraints.vertical === VerticalConstraint.TopBottom
? `width: 92pt; x: 1085; fill: ${SELECTED_COLOR};`
: `fill: ${selectionBoxColor};`}
`,
bottomConstraint: css`
${currentConstraints.vertical === VerticalConstraint.Bottom
${currentConstraints.vertical === VerticalConstraint.Bottom ||
currentConstraints.vertical === VerticalConstraint.TopBottom
? `width: 92pt; x: 1085; fill: ${SELECTED_COLOR};`
: `fill: ${selectionBoxColor};`}
`,
leftConstraint: css`
${currentConstraints.horizontal === HorizontalConstraint.Left
${currentConstraints.horizontal === HorizontalConstraint.Left ||
currentConstraints.horizontal === HorizontalConstraint.LeftRight
? `height: 92pt; y: 1014; fill: ${SELECTED_COLOR};`
: `fill: ${selectionBoxColor};`}
`,
rightConstraint: css`
${currentConstraints.horizontal === HorizontalConstraint.Right
${currentConstraints.horizontal === HorizontalConstraint.Right ||
currentConstraints.horizontal === HorizontalConstraint.LeftRight
? `height: 92pt; y: 1014; fill: ${SELECTED_COLOR};`
: `fill: ${selectionBoxColor};`}
`,