Canvas: Position inline editor default via panel dimensions and add context menu option (#51471)

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Drew Slobodnjak 2022-07-07 16:16:22 -07:00 committed by GitHub
parent 63366615bb
commit dec3c3a5b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 8 deletions

View File

@ -63,6 +63,8 @@ export class Scene {
isPanelEditing = locationService.getSearchObject().editPanel !== undefined; isPanelEditing = locationService.getSearchObject().editPanel !== undefined;
inlineEditingCallback?: () => void;
constructor(cfg: CanvasFrameOptions, enableEditing: boolean, public onSave: (cfg: CanvasFrameOptions) => void) { constructor(cfg: CanvasFrameOptions, enableEditing: boolean, public onSave: (cfg: CanvasFrameOptions) => void) {
this.root = this.load(cfg, enableEditing); this.root = this.load(cfg, enableEditing);
} }

View File

@ -1,11 +1,13 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useObservable } from 'react-use';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { ContextMenu, MenuItem } from '@grafana/ui'; import { ContextMenu, MenuItem } from '@grafana/ui';
import { Scene } from '../../../features/canvas/runtime/scene'; import { Scene } from '../../../features/canvas/runtime/scene';
import { activePanelSubject } from './CanvasPanel';
import { LayerActionID } from './types'; import { LayerActionID } from './types';
type Props = { type Props = {
@ -18,6 +20,8 @@ type AnchorPoint = {
}; };
export const CanvasContextMenu = ({ scene }: Props) => { export const CanvasContextMenu = ({ scene }: Props) => {
const activePanel = useObservable(activePanelSubject);
const inlineEditorOpen = activePanel?.panel.state.openInlineEdit;
const [isMenuVisible, setIsMenuVisible] = useState<boolean>(false); const [isMenuVisible, setIsMenuVisible] = useState<boolean>(false);
const [anchorPoint, setAnchorPoint] = useState<AnchorPoint>({ x: 0, y: 0 }); const [anchorPoint, setAnchorPoint] = useState<AnchorPoint>({ x: 0, y: 0 });
@ -87,6 +91,20 @@ export const CanvasContextMenu = ({ scene }: Props) => {
}} }}
className={styles.menuItem} className={styles.menuItem}
/> />
<MenuItem
label={inlineEditorOpen ? 'Close Editor' : 'Open Editor'}
onClick={() => {
if (scene.inlineEditingCallback) {
if (inlineEditorOpen) {
activePanel.panel.inlineEditButtonClose();
} else {
scene.inlineEditingCallback();
}
}
closeContextMenu();
}}
className={styles.menuItem}
/>
</> </>
); );
}; };

View File

@ -57,6 +57,7 @@ export class CanvasPanel extends Component<Props, State> {
this.scene = new Scene(this.props.options.root, this.props.options.inlineEditing, this.onUpdateScene); this.scene = new Scene(this.props.options.root, this.props.options.inlineEditing, this.onUpdateScene);
this.scene.updateSize(props.width, props.height); this.scene.updateSize(props.width, props.height);
this.scene.updateData(props.data); this.scene.updateData(props.data);
this.scene.inlineEditingCallback = this.inlineEditButtonClick;
this.subs.add( this.subs.add(
this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt) => { this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt) => {
@ -192,7 +193,9 @@ export class CanvasPanel extends Component<Props, State> {
}; };
renderInlineEdit = () => { renderInlineEdit = () => {
return <InlineEdit onClose={() => this.inlineEditButtonClose()} />; return (
<InlineEdit onClose={() => this.inlineEditButtonClose()} id={this.props.id} scene={activeCanvasPanel!.scene} />
);
}; };
render() { render() {

View File

@ -1,29 +1,35 @@
import { css } from '@emotion/css'; 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 Draggable from 'react-draggable';
import { Resizable, ResizeCallbackData } from 'react-resizable'; import { Resizable, ResizeCallbackData } from 'react-resizable';
import { Dimensions2D, GrafanaTheme2 } from '@grafana/data'; import { Dimensions2D, GrafanaTheme2 } from '@grafana/data';
import { IconButton, Portal, useStyles2 } from '@grafana/ui'; import { IconButton, Portal, useStyles2 } from '@grafana/ui';
import store from 'app/core/store'; import store from 'app/core/store';
import { Scene } from 'app/features/canvas/runtime/scene';
import { InlineEditBody } from './InlineEditBody'; import { InlineEditBody } from './InlineEditBody';
type Props = { type Props = {
onClose?: () => void; onClose?: () => void;
id: number;
scene: Scene;
}; };
const OFFSET_X = 70; const OFFSET_X = 10;
const OFFSET_Y = 32;
export const InlineEdit = ({ onClose }: Props) => { export const InlineEdit = ({ onClose, id, scene }: Props) => {
const btnInlineEdit = document.querySelector('[data-btninlineedit]')!.getBoundingClientRect(); const root = scene.root.div!.getBoundingClientRect();
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const inlineEditKey = 'inlineEditPanel'; const inlineEditKey = 'inlineEditPanel' + id.toString();
const defaultMeasurements = { width: 350, height: 400 }; const defaultMeasurements = { width: 350, height: 400 };
const defaultX = btnInlineEdit.x + OFFSET_X; const defaultX = root.x + root.width - defaultMeasurements.width - OFFSET_X;
const defaultY = btnInlineEdit.y - defaultMeasurements.height; const defaultY = root.y + OFFSET_Y;
const savedPlacement = store.getObject(inlineEditKey, { const savedPlacement = store.getObject(inlineEditKey, {
x: defaultX, x: defaultX,
@ -34,6 +40,18 @@ export const InlineEdit = ({ onClose }: Props) => {
const [measurements, setMeasurements] = useState<Dimensions2D>({ width: savedPlacement.w, height: savedPlacement.h }); const [measurements, setMeasurements] = useState<Dimensions2D>({ width: savedPlacement.w, height: savedPlacement.h });
const [placement, setPlacement] = useState({ x: savedPlacement.x, y: savedPlacement.y }); 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) => { const onDragStop = (event: any, dragElement: any) => {
let x = dragElement.x < 0 ? 0 : dragElement.x; let x = dragElement.x < 0 ? 0 : dragElement.x;
let y = dragElement.y < 0 ? 0 : dragElement.y; let y = dragElement.y < 0 ? 0 : dragElement.y;