From dec3c3a5b14ba3ff2bfea323c763c15afdf273cd Mon Sep 17 00:00:00 2001 From: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:16:22 -0700 Subject: [PATCH] Canvas: Position inline editor default via panel dimensions and add context menu option (#51471) Co-authored-by: nmarrs --- public/app/features/canvas/runtime/scene.tsx | 2 ++ .../panel/canvas/CanvasContextMenu.tsx | 18 +++++++++++ .../app/plugins/panel/canvas/CanvasPanel.tsx | 5 ++- .../app/plugins/panel/canvas/InlineEdit.tsx | 32 +++++++++++++++---- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index d481950b742..a167a3992eb 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -63,6 +63,8 @@ export class Scene { isPanelEditing = locationService.getSearchObject().editPanel !== undefined; + inlineEditingCallback?: () => void; + constructor(cfg: CanvasFrameOptions, enableEditing: boolean, public onSave: (cfg: CanvasFrameOptions) => void) { this.root = this.load(cfg, enableEditing); } diff --git a/public/app/plugins/panel/canvas/CanvasContextMenu.tsx b/public/app/plugins/panel/canvas/CanvasContextMenu.tsx index 3124406ccb5..bfdd0b6a4d9 100644 --- a/public/app/plugins/panel/canvas/CanvasContextMenu.tsx +++ b/public/app/plugins/panel/canvas/CanvasContextMenu.tsx @@ -1,11 +1,13 @@ import { css } from '@emotion/css'; import React, { useCallback, useEffect, useState } from 'react'; +import { useObservable } from 'react-use'; import { first } from 'rxjs/operators'; import { ContextMenu, MenuItem } from '@grafana/ui'; import { Scene } from '../../../features/canvas/runtime/scene'; +import { activePanelSubject } from './CanvasPanel'; import { LayerActionID } from './types'; type Props = { @@ -18,6 +20,8 @@ type AnchorPoint = { }; export const CanvasContextMenu = ({ scene }: Props) => { + const activePanel = useObservable(activePanelSubject); + const inlineEditorOpen = activePanel?.panel.state.openInlineEdit; const [isMenuVisible, setIsMenuVisible] = useState(false); const [anchorPoint, setAnchorPoint] = useState({ x: 0, y: 0 }); @@ -87,6 +91,20 @@ export const CanvasContextMenu = ({ scene }: Props) => { }} className={styles.menuItem} /> + { + if (scene.inlineEditingCallback) { + if (inlineEditorOpen) { + activePanel.panel.inlineEditButtonClose(); + } else { + scene.inlineEditingCallback(); + } + } + closeContextMenu(); + }} + className={styles.menuItem} + /> ); }; diff --git a/public/app/plugins/panel/canvas/CanvasPanel.tsx b/public/app/plugins/panel/canvas/CanvasPanel.tsx index b04d25bc77a..16fb4cb1c82 100644 --- a/public/app/plugins/panel/canvas/CanvasPanel.tsx +++ b/public/app/plugins/panel/canvas/CanvasPanel.tsx @@ -57,6 +57,7 @@ export class CanvasPanel extends Component { this.scene = new Scene(this.props.options.root, this.props.options.inlineEditing, this.onUpdateScene); this.scene.updateSize(props.width, props.height); this.scene.updateData(props.data); + this.scene.inlineEditingCallback = this.inlineEditButtonClick; this.subs.add( this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt) => { @@ -192,7 +193,9 @@ export class CanvasPanel extends Component { }; renderInlineEdit = () => { - return this.inlineEditButtonClose()} />; + return ( + this.inlineEditButtonClose()} id={this.props.id} scene={activeCanvasPanel!.scene} /> + ); }; render() { diff --git a/public/app/plugins/panel/canvas/InlineEdit.tsx b/public/app/plugins/panel/canvas/InlineEdit.tsx index d3249248c4b..1d16d230882 100644 --- a/public/app/plugins/panel/canvas/InlineEdit.tsx +++ b/public/app/plugins/panel/canvas/InlineEdit.tsx @@ -1,29 +1,35 @@ import { css } from '@emotion/css'; -import React, { SyntheticEvent, useRef, useState } from 'react'; +import React, { SyntheticEvent, useEffect, useRef, useState } from 'react'; import Draggable from 'react-draggable'; import { Resizable, ResizeCallbackData } from 'react-resizable'; import { Dimensions2D, GrafanaTheme2 } from '@grafana/data'; import { IconButton, Portal, useStyles2 } from '@grafana/ui'; import store from 'app/core/store'; +import { Scene } from 'app/features/canvas/runtime/scene'; import { InlineEditBody } from './InlineEditBody'; type Props = { onClose?: () => void; + id: number; + scene: Scene; }; -const OFFSET_X = 70; +const OFFSET_X = 10; +const OFFSET_Y = 32; -export const InlineEdit = ({ onClose }: Props) => { - const btnInlineEdit = document.querySelector('[data-btninlineedit]')!.getBoundingClientRect(); +export const InlineEdit = ({ onClose, id, scene }: Props) => { + const root = scene.root.div!.getBoundingClientRect(); + const windowHeight = window.innerHeight; + const windowWidth = window.innerWidth; const ref = useRef(null); const styles = useStyles2(getStyles); - const inlineEditKey = 'inlineEditPanel'; + const inlineEditKey = 'inlineEditPanel' + id.toString(); const defaultMeasurements = { width: 350, height: 400 }; - const defaultX = btnInlineEdit.x + OFFSET_X; - const defaultY = btnInlineEdit.y - defaultMeasurements.height; + const defaultX = root.x + root.width - defaultMeasurements.width - OFFSET_X; + const defaultY = root.y + OFFSET_Y; const savedPlacement = store.getObject(inlineEditKey, { x: defaultX, @@ -34,6 +40,18 @@ export const InlineEdit = ({ onClose }: Props) => { const [measurements, setMeasurements] = useState({ width: savedPlacement.w, height: savedPlacement.h }); const [placement, setPlacement] = useState({ x: savedPlacement.x, y: savedPlacement.y }); + // Checks that placement is within browser window + useEffect(() => { + const minX = windowWidth - measurements.width - OFFSET_X; + const minY = windowHeight - measurements.height - OFFSET_Y; + if (minX < placement.x && minX > 0) { + setPlacement({ ...placement, x: minX }); + } + if (minY < placement.y && minY > 0) { + setPlacement({ ...placement, y: minY }); + } + }, [windowHeight, windowWidth, placement, measurements]); + const onDragStop = (event: any, dragElement: any) => { let x = dragElement.x < 0 ? 0 : dragElement.x; let y = dragElement.y < 0 ? 0 : dragElement.y;