import { css } from '@emotion/css'; import React, { SyntheticEvent, useEffect, useRef, useState } from 'react'; import Draggable, { DraggableEventHandler } 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 = 10; const OFFSET_Y = 32; export function 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' + id.toString(); const defaultMeasurements = { width: 400, height: 400 }; const widthOffset = root?.width ?? defaultMeasurements.width + OFFSET_X * 2; const defaultX = root?.x ?? 0 + widthOffset - defaultMeasurements.width - OFFSET_X; const defaultY = root?.y ?? 0 + OFFSET_Y; const savedPlacement = store.getObject(inlineEditKey, { x: defaultX, y: defaultY, w: defaultMeasurements.width, h: defaultMeasurements.height, }); 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: DraggableEventHandler = (event, dragElement) => { let x = dragElement.x < 0 ? 0 : dragElement.x; let y = dragElement.y < 0 ? 0 : dragElement.y; setPlacement({ x: x, y: y }); saveToStore(x, y, measurements.width, measurements.height); }; const onResizeStop = (event: SyntheticEvent, data: ResizeCallbackData) => { const { size } = data; setMeasurements({ width: size.width, height: size.height }); saveToStore(placement.x, placement.y, size.width, size.height); }; const saveToStore = (x: number, y: number, width: number, height: number) => { store.setObject(inlineEditKey, { x: x, y: y, w: width, h: height }); }; return (
Canvas Inline Editor
); } const getStyles = (theme: GrafanaTheme2) => ({ inlineEditorContainer: css({ display: 'flex', flexDirection: 'column', background: theme.components.panel.background, border: `1px solid ${theme.colors.border.weak}`, boxShadow: theme.shadows.z3, zIndex: 1000, opacity: 1, minWidth: '400px', }), draggableWrapper: css({ width: 0, height: 0, }), inlineEditorHeader: css({ display: 'flex', alignItems: 'center', justifyContent: 'center', background: theme.colors.background.canvas, borderBottom: `1px solid ${theme.colors.border.weak}`, height: '40px', cursor: 'move', }), inlineEditorContent: css({ whiteSpace: 'pre-wrap', padding: '10px', }), inlineEditorClose: css({ marginLeft: 'auto', }), placeholder: css({ width: '24px', height: '24px', visibility: 'hidden', marginRight: 'auto', }), inlineEditorContentWrapper: css({ overflow: 'scroll', }), });