fix: resolve issue #136 where "Add Node" popup has huge offset (#195)

* fix: "Add node" popup has huge offset

* fix: resolve issue #136 where "Add Node" popup has huge offset

* Add check for empty cell

Thanks @abrar74774
This commit is contained in:
Abrar74774
2026-01-04 01:29:55 +03:00
committed by GitHub
parent 6b83ef776c
commit fa5478e709
3 changed files with 66 additions and 72 deletions

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { Menu, MenuItem } from '@mui/material';
import { Coords } from 'src/types';
interface MenuItemI {
label: string;
@@ -9,25 +8,19 @@ interface MenuItemI {
interface Props {
onClose: () => void;
position: Coords;
anchorEl?: HTMLElement;
anchorEl?: HTMLElement | null;
menuItems: MenuItemI[];
}
export const ContextMenu = ({
onClose,
position,
anchorEl,
menuItems
}: Props) => {
return (
<Menu
open
open={!!anchorEl}
anchorEl={anchorEl}
style={{
left: position.x,
top: position.y
}}
onClose={onClose}
>
{menuItems.map((item, index) => {

View File

@@ -1,13 +1,13 @@
import React, { useCallback } from 'react';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { getTilePosition, CoordsUtils, generateId, findNearestUnoccupiedTile } from 'src/utils';
import { generateId, findNearestUnoccupiedTile } from 'src/utils';
import { useScene } from 'src/hooks/useScene';
import { useModelStore } from 'src/stores/modelStore';
import { VIEW_ITEM_DEFAULTS } from 'src/config';
import { ContextMenu } from './ContextMenu';
interface Props {
anchorEl?: HTMLElement;
anchorEl?: HTMLElement | null;
}
export const ContextMenuManager = ({ anchorEl }: Props) => {
@@ -15,9 +15,6 @@ export const ContextMenuManager = ({ anchorEl }: Props) => {
const model = useModelStore((state) => {
return state;
});
const zoom = useUiStateStore((state) => {
return state.zoom;
});
const contextMenu = useUiStateStore((state) => {
return state.contextMenu;
});
@@ -30,67 +27,58 @@ export const ContextMenuManager = ({ anchorEl }: Props) => {
uiStateActions.setContextMenu(null);
}, [uiStateActions]);
if (!contextMenu) {
return null;
}
return (
<ContextMenu
anchorEl={anchorEl}
onClose={onClose}
menuItems={[
{
label: 'Add Node',
onClick: () => {
if (!contextMenu) return;
if (model.icons.length > 0) {
const modelItemId = generateId();
const firstIcon = model.icons[0];
// Find nearest unoccupied tile (should return the same tile since context menu is for empty tiles)
const targetTile = findNearestUnoccupiedTile(contextMenu.tile, scene) || contextMenu.tile;
if (contextMenu.type === 'EMPTY') {
return (
<ContextMenu
anchorEl={anchorEl}
onClose={onClose}
position={CoordsUtils.multiply(
getTilePosition({ tile: contextMenu.tile }),
zoom
)}
menuItems={[
{
label: 'Add Node',
onClick: () => {
if (model.icons.length > 0) {
const modelItemId = generateId();
const firstIcon = model.icons[0];
// Find nearest unoccupied tile (should return the same tile since context menu is for empty tiles)
const targetTile = findNearestUnoccupiedTile(contextMenu.tile, scene) || contextMenu.tile;
scene.placeIcon({
modelItem: {
id: modelItemId,
name: 'Untitled',
icon: firstIcon.id
},
viewItem: {
...VIEW_ITEM_DEFAULTS,
id: modelItemId,
tile: targetTile
}
});
}
onClose();
}
},
{
label: 'Add Rectangle',
onClick: () => {
if (model.colors.length > 0) {
scene.createRectangle({
id: generateId(),
color: model.colors[0].id,
from: contextMenu.tile,
to: contextMenu.tile
});
}
onClose();
scene.placeIcon({
modelItem: {
id: modelItemId,
name: 'Untitled',
icon: firstIcon.id
},
viewItem: {
...VIEW_ITEM_DEFAULTS,
id: modelItemId,
tile: targetTile
}
});
}
onClose();
}
]}
/>
);
}
},
{
label: 'Add Rectangle',
onClick: () => {
if (!contextMenu) return;
if (model.colors.length > 0) {
scene.createRectangle({
id: generateId(),
color: model.colors[0].id,
from: contextMenu.tile,
to: contextMenu.tile
});
}
onClose();
}
}
]}
/>
);
// Remove ITEM context menu since layer ordering only works for rectangles
// and provides no value for regular diagram items
return null;
};

View File

@@ -24,6 +24,7 @@ import { ConnectorRerouteTooltip } from '../ConnectorRerouteTooltip/ConnectorRer
import { ImportHintTooltip } from '../ImportHintTooltip/ImportHintTooltip';
import { LassoHintTooltip } from '../LassoHintTooltip/LassoHintTooltip';
import { LazyLoadingWelcomeNotification } from '../LazyLoadingWelcomeNotification/LazyLoadingWelcomeNotification';
import { CoordsUtils, getTilePosition } from 'src/utils';
const ToolsEnum = {
MAIN_MENU: 'MAIN_MENU',
@@ -100,6 +101,9 @@ export const UiOverlay = () => {
const iconPackManager = useUiStateStore((state) => {
return state.iconPackManager;
});
const contextMenu = useUiStateStore((state) => {
return state.contextMenu;
});
const { size: rendererSize } = useResizeObserver(rendererEl);
return (
@@ -262,8 +266,17 @@ export const UiOverlay = () => {
{iconPackManager && <LazyLoadingWelcomeNotification />}
<SceneLayer>
<Box ref={contextMenuAnchorRef} />
<ContextMenuManager anchorEl={contextMenuAnchorRef.current} />
{contextMenu && (
<Box
ref={contextMenuAnchorRef}
sx={{
position: 'absolute',
left: getTilePosition({ tile: contextMenu.tile }).x,
top: getTilePosition({ tile: contextMenu.tile }).y
}}
/>
)}
<ContextMenuManager anchorEl={contextMenu && contextMenu.type === "EMPTY" ? contextMenuAnchorRef.current : null} />
</SceneLayer>
</>
);