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;
inlineEditingCallback?: () => void;
constructor(cfg: CanvasFrameOptions, enableEditing: boolean, public onSave: (cfg: CanvasFrameOptions) => void) {
this.root = this.load(cfg, enableEditing);
}

View File

@ -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<boolean>(false);
const [anchorPoint, setAnchorPoint] = useState<AnchorPoint>({ x: 0, y: 0 });
@ -87,6 +91,20 @@ export const CanvasContextMenu = ({ scene }: Props) => {
}}
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.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<Props, State> {
};
renderInlineEdit = () => {
return <InlineEdit onClose={() => this.inlineEditButtonClose()} />;
return (
<InlineEdit onClose={() => this.inlineEditButtonClose()} id={this.props.id} scene={activeCanvasPanel!.scene} />
);
};
render() {

View File

@ -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<HTMLDivElement>(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<Dimensions2D>({ 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;