mirror of https://github.com/grafana/grafana
Canvas: Context menu (#48909)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: nmarrs <nathanielmarrs@gmail.com>pull/49357/head
parent
dea6fb4c1b
commit
b3b650be1f
@ -0,0 +1,143 @@ |
||||
import { css } from '@emotion/css'; |
||||
import React, { useCallback, useEffect, useState } from 'react'; |
||||
import { first } from 'rxjs/operators'; |
||||
|
||||
import { ContextMenu, MenuItem } from '@grafana/ui'; |
||||
|
||||
import { Scene } from '../../../features/canvas/runtime/scene'; |
||||
|
||||
import { LayerActionID } from './types'; |
||||
|
||||
type Props = { |
||||
scene: Scene; |
||||
}; |
||||
|
||||
type AnchorPoint = { |
||||
x: number; |
||||
y: number; |
||||
}; |
||||
|
||||
export const CanvasContextMenu = ({ scene }: Props) => { |
||||
const [isMenuVisible, setIsMenuVisible] = useState<boolean>(false); |
||||
const [anchorPoint, setAnchorPoint] = useState<AnchorPoint>({ x: 0, y: 0 }); |
||||
|
||||
const styles = getStyles(); |
||||
|
||||
const selectedElements = scene.selecto?.getSelectedTargets(); |
||||
|
||||
const handleContextMenu = useCallback( |
||||
(event) => { |
||||
event.preventDefault(); |
||||
if (event.currentTarget) { |
||||
scene.select({ targets: [event.currentTarget as HTMLElement | SVGElement] }); |
||||
} |
||||
setAnchorPoint({ x: event.pageX, y: event.pageY }); |
||||
setIsMenuVisible(true); |
||||
}, |
||||
[scene] |
||||
); |
||||
|
||||
useEffect(() => { |
||||
if (selectedElements && selectedElements.length === 1) { |
||||
const element = selectedElements[0]; |
||||
element.addEventListener('contextmenu', handleContextMenu); |
||||
} |
||||
}, [selectedElements, handleContextMenu]); |
||||
|
||||
if (!selectedElements) { |
||||
return <></>; |
||||
} |
||||
|
||||
const closeContextMenu = () => { |
||||
setIsMenuVisible(false); |
||||
}; |
||||
|
||||
const renderMenuItems = () => { |
||||
return ( |
||||
<> |
||||
<MenuItem |
||||
label="Delete" |
||||
onClick={() => { |
||||
contextMenuAction(LayerActionID.Delete); |
||||
closeContextMenu(); |
||||
}} |
||||
className={styles.menuItem} |
||||
/> |
||||
<MenuItem |
||||
label="Duplicate" |
||||
onClick={() => { |
||||
contextMenuAction(LayerActionID.Duplicate); |
||||
closeContextMenu(); |
||||
}} |
||||
className={styles.menuItem} |
||||
/> |
||||
<MenuItem |
||||
label="Bring to front" |
||||
onClick={() => { |
||||
contextMenuAction(LayerActionID.MoveTop); |
||||
closeContextMenu(); |
||||
}} |
||||
className={styles.menuItem} |
||||
/> |
||||
<MenuItem |
||||
label="Send to back" |
||||
onClick={() => { |
||||
contextMenuAction(LayerActionID.MoveBottom); |
||||
closeContextMenu(); |
||||
}} |
||||
className={styles.menuItem} |
||||
/> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
const contextMenuAction = (actionType: string) => { |
||||
scene.selection.pipe(first()).subscribe((currentSelectedElements) => { |
||||
const currentSelectedElement = currentSelectedElements[0]; |
||||
const currentLayer = currentSelectedElement.parent!; |
||||
|
||||
switch (actionType) { |
||||
case LayerActionID.Delete: |
||||
currentLayer.doAction(LayerActionID.Delete, currentSelectedElement); |
||||
break; |
||||
case LayerActionID.Duplicate: |
||||
currentLayer.doAction(LayerActionID.Duplicate, currentSelectedElement); |
||||
break; |
||||
case LayerActionID.MoveTop: |
||||
currentLayer.doAction(LayerActionID.MoveTop, currentSelectedElement); |
||||
break; |
||||
case LayerActionID.MoveBottom: |
||||
currentLayer.doAction(LayerActionID.MoveBottom, currentSelectedElement); |
||||
break; |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
if (isMenuVisible) { |
||||
return ( |
||||
<div |
||||
onContextMenu={(event) => { |
||||
event.preventDefault(); |
||||
closeContextMenu(); |
||||
}} |
||||
> |
||||
<ContextMenu |
||||
x={anchorPoint.x} |
||||
y={anchorPoint.y} |
||||
onClose={closeContextMenu} |
||||
renderMenuItems={renderMenuItems} |
||||
focusOnOpen={false} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
return <></>; |
||||
}; |
||||
|
||||
const getStyles = () => ({ |
||||
menuItem: css` |
||||
max-width: 60ch; |
||||
overflow: hidden; |
||||
`,
|
||||
}); |
Loading…
Reference in new issue