diff --git a/public/app/features/canvas/runtime/ables.tsx b/public/app/features/canvas/runtime/ables.tsx new file mode 100644 index 00000000000..a31520ed2ce --- /dev/null +++ b/public/app/features/canvas/runtime/ables.tsx @@ -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, React: Renderer) { + const rect = moveable.getRect(); + return ( +
+ {Math.round(rect.offsetWidth)} x {Math.round(rect.offsetHeight)} +
+ ); + }, +}; + +export const constraintViewable = (scene: Scene) => ({ + name: 'constraintViewable', + props: {}, + events: {}, + render(moveable: MoveableManagerInterface, 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; + }, +}); diff --git a/public/app/features/canvas/runtime/element.tsx b/public/app/features/canvas/runtime/element.tsx index 03257a75153..2578f3d2f35 100644 --- a/public/app/features/canvas/runtime/element.tsx +++ b/public/app/features/canvas/runtime/element.tsx @@ -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; diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index dd66f93a677..5bd9aa2efce 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -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; - `, })); diff --git a/public/app/plugins/panel/canvas/editor/ConstraintSelectionBox.tsx b/public/app/plugins/panel/canvas/editor/ConstraintSelectionBox.tsx index 8c815bc5c98..fb6b5497568 100644 --- a/public/app/plugins/panel/canvas/editor/ConstraintSelectionBox.tsx +++ b/public/app/plugins/panel/canvas/editor/ConstraintSelectionBox.tsx @@ -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};`} `,