2022-05-05 03:15:07 +08:00
|
|
|
import { css } from '@emotion/css';
|
2022-07-08 07:16:22 +08:00
|
|
|
import React, { SyntheticEvent, useEffect, useRef, useState } from 'react';
|
2023-05-22 18:53:58 +08:00
|
|
|
import Draggable, { DraggableEventHandler } from 'react-draggable';
|
2022-05-05 03:15:07 +08:00
|
|
|
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';
|
2022-07-08 07:16:22 +08:00
|
|
|
import { Scene } from 'app/features/canvas/runtime/scene';
|
2022-05-05 03:15:07 +08:00
|
|
|
|
|
|
|
|
import { InlineEditBody } from './InlineEditBody';
|
|
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
|
onClose?: () => void;
|
2022-07-08 07:16:22 +08:00
|
|
|
id: number;
|
|
|
|
|
scene: Scene;
|
2022-05-05 03:15:07 +08:00
|
|
|
};
|
|
|
|
|
|
2022-07-08 07:16:22 +08:00
|
|
|
const OFFSET_X = 10;
|
|
|
|
|
const OFFSET_Y = 32;
|
2022-05-05 03:15:07 +08:00
|
|
|
|
2022-07-28 07:20:39 +08:00
|
|
|
export function InlineEdit({ onClose, id, scene }: Props) {
|
2022-10-01 00:52:30 +08:00
|
|
|
const root = scene.root.div?.getBoundingClientRect();
|
2022-07-08 07:16:22 +08:00
|
|
|
const windowHeight = window.innerHeight;
|
|
|
|
|
const windowWidth = window.innerWidth;
|
2022-05-05 03:15:07 +08:00
|
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
|
const styles = useStyles2(getStyles);
|
2022-07-08 07:16:22 +08:00
|
|
|
const inlineEditKey = 'inlineEditPanel' + id.toString();
|
2022-05-05 03:15:07 +08:00
|
|
|
|
2022-11-04 01:30:12 +08:00
|
|
|
const defaultMeasurements = { width: 400, height: 400 };
|
2022-10-01 00:52:30 +08:00
|
|
|
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;
|
2022-05-05 03:15:07 +08:00
|
|
|
|
|
|
|
|
const savedPlacement = store.getObject(inlineEditKey, {
|
|
|
|
|
x: defaultX,
|
|
|
|
|
y: defaultY,
|
|
|
|
|
w: defaultMeasurements.width,
|
|
|
|
|
h: defaultMeasurements.height,
|
|
|
|
|
});
|
|
|
|
|
const [measurements, setMeasurements] = useState<Dimensions2D>({ width: savedPlacement.w, height: savedPlacement.h });
|
|
|
|
|
const [placement, setPlacement] = useState({ x: savedPlacement.x, y: savedPlacement.y });
|
|
|
|
|
|
2022-07-08 07:16:22 +08:00
|
|
|
// 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]);
|
|
|
|
|
|
2023-05-22 18:53:58 +08:00
|
|
|
const onDragStop: DraggableEventHandler = (event, dragElement) => {
|
2022-05-05 03:15:07 +08:00
|
|
|
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<Element, Event>, 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 (
|
|
|
|
|
<Portal>
|
|
|
|
|
<div className={styles.draggableWrapper}>
|
|
|
|
|
<Draggable handle="strong" onStop={onDragStop} position={{ x: placement.x, y: placement.y }}>
|
|
|
|
|
<Resizable height={measurements.height} width={measurements.width} onResize={onResizeStop}>
|
|
|
|
|
<div
|
|
|
|
|
className={styles.inlineEditorContainer}
|
|
|
|
|
style={{ height: `${measurements.height}px`, width: `${measurements.width}px` }}
|
|
|
|
|
ref={ref}
|
|
|
|
|
>
|
|
|
|
|
<strong className={styles.inlineEditorHeader}>
|
|
|
|
|
<div className={styles.placeholder} />
|
|
|
|
|
<div>Canvas Inline Editor</div>
|
2023-06-08 16:23:28 +08:00
|
|
|
<IconButton
|
|
|
|
|
name="times"
|
|
|
|
|
size="xl"
|
|
|
|
|
className={styles.inlineEditorClose}
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
tooltip="Close inline editor"
|
|
|
|
|
/>
|
2022-05-05 03:15:07 +08:00
|
|
|
</strong>
|
|
|
|
|
<div className={styles.inlineEditorContentWrapper}>
|
|
|
|
|
<div className={styles.inlineEditorContent}>
|
|
|
|
|
<InlineEditBody />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Resizable>
|
|
|
|
|
</Draggable>
|
|
|
|
|
</div>
|
|
|
|
|
</Portal>
|
|
|
|
|
);
|
2022-07-28 07:20:39 +08:00
|
|
|
}
|
2022-05-05 03:15:07 +08:00
|
|
|
|
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => ({
|
|
|
|
|
inlineEditorContainer: css`
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2022-10-26 20:28:12 +08:00
|
|
|
background: ${theme.components.panel.background};
|
2023-06-15 02:39:59 +08:00
|
|
|
border: 1px solid ${theme.colors.border.weak};
|
2023-06-12 23:11:42 +08:00
|
|
|
box-shadow: ${theme.shadows.z3};
|
2022-05-05 03:15:07 +08:00
|
|
|
z-index: 1000;
|
|
|
|
|
opacity: 1;
|
2023-01-10 01:24:12 +08:00
|
|
|
min-width: 400px;
|
2022-05-05 03:15:07 +08:00
|
|
|
`,
|
|
|
|
|
draggableWrapper: css`
|
|
|
|
|
width: 0;
|
|
|
|
|
height: 0;
|
|
|
|
|
`,
|
|
|
|
|
inlineEditorHeader: css`
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
background: ${theme.colors.background.canvas};
|
2023-06-15 02:39:59 +08:00
|
|
|
border-bottom: 1px solid ${theme.colors.border.weak};
|
2022-05-05 03:15:07 +08:00
|
|
|
height: 40px;
|
|
|
|
|
cursor: move;
|
|
|
|
|
`,
|
|
|
|
|
inlineEditorContent: css`
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
`,
|
|
|
|
|
inlineEditorClose: css`
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
`,
|
|
|
|
|
placeholder: css`
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
visibility: hidden;
|
|
|
|
|
margin-right: auto;
|
|
|
|
|
`,
|
|
|
|
|
inlineEditorContentWrapper: css`
|
|
|
|
|
overflow: scroll;
|
|
|
|
|
`,
|
|
|
|
|
});
|