mirror of
https://github.com/encounter/objdiff-web.git
synced 2026-07-10 12:18:37 -07:00
Add symbol and instruction context menus
This commit is contained in:
+1
-2
@@ -20,11 +20,10 @@
|
||||
"extension:publish": "pnpm run extension:build && vsce publish --no-dependencies"
|
||||
},
|
||||
"dependencies": {
|
||||
"@protobuf-ts/runtime": "^2.9.4",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"clsx": "^2.1.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"objdiff-wasm": "3.0.0-beta.1",
|
||||
"objdiff-wasm": "3.0.0-beta.3",
|
||||
"picomatch": "^4.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
Generated
+5
-13
@@ -8,9 +8,6 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@protobuf-ts/runtime':
|
||||
specifier: ^2.9.4
|
||||
version: 2.9.4
|
||||
'@vscode/codicons':
|
||||
specifier: ^0.0.36
|
||||
version: 0.0.36
|
||||
@@ -21,8 +18,8 @@ importers:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
objdiff-wasm:
|
||||
specifier: 3.0.0-beta.1
|
||||
version: 3.0.0-beta.1
|
||||
specifier: 3.0.0-beta.3
|
||||
version: 3.0.0-beta.3
|
||||
picomatch:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
@@ -428,9 +425,6 @@ packages:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@protobuf-ts/runtime@2.9.4':
|
||||
resolution: {integrity: sha512-vHRFWtJJB/SiogWDF0ypoKfRIZ41Kq+G9cEFj6Qm1eQaAhJ1LDFvgZ7Ja4tb3iLOQhz0PaoPnnOijF1qmEqTxg==}
|
||||
|
||||
'@rsbuild/core@1.2.14':
|
||||
resolution: {integrity: sha512-G8AqvCHBhs8Yt7pOQuS5YYjdYozp436ohaLr1iAMB/Jw01VNFh4u1tpShudRnw3NPmHhJ82wBaEu1zNaJ0VsKg==}
|
||||
engines: {node: '>=16.7.0'}
|
||||
@@ -1406,8 +1400,8 @@ packages:
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
objdiff-wasm@3.0.0-beta.1:
|
||||
resolution: {integrity: sha512-1x97N/QUCeQJxm/ZcI+Frp1Wxsi0heKZQHR2kQNhpwcCvt84DainLIJ+4nH6DOU6U4eAxkUVPKvPbW1pAPqZZA==}
|
||||
objdiff-wasm@3.0.0-beta.3:
|
||||
resolution: {integrity: sha512-StNnXtstWdM8Bc2eWxTJhC/9pdQj2GZaAeL2KXASZzXvmwzGqWxaW+RbZ3z+tOfQAgOR4QUQnnjLzpXdQA/Rlw==}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
@@ -2180,8 +2174,6 @@ snapshots:
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@protobuf-ts/runtime@2.9.4': {}
|
||||
|
||||
'@rsbuild/core@1.2.14':
|
||||
dependencies:
|
||||
'@rspack/core': 1.2.5(@swc/helpers@0.5.15)
|
||||
@@ -3229,7 +3221,7 @@ snapshots:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
|
||||
objdiff-wasm@3.0.0-beta.1: {}
|
||||
objdiff-wasm@3.0.0-beta.3: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
|
||||
+4
-4
@@ -18,6 +18,7 @@ const webConfig: RsbuildConfig = {
|
||||
},
|
||||
},
|
||||
html: {
|
||||
scriptLoading: 'module',
|
||||
title: 'objdiff',
|
||||
},
|
||||
plugins: [pluginReact(), pluginTypeCheck(), pluginTypedCSSModules()],
|
||||
@@ -67,11 +68,8 @@ const extensionConfig: RsbuildConfig = {
|
||||
inlineStyles: true,
|
||||
legalComments: 'none',
|
||||
},
|
||||
// <script defer> doesn't work with inline scripts,
|
||||
// so we need to move the scripts to the body.
|
||||
html: {
|
||||
inject: 'body',
|
||||
scriptLoading: 'blocking',
|
||||
scriptLoading: 'module',
|
||||
title: 'objdiff',
|
||||
},
|
||||
plugins: [
|
||||
@@ -129,8 +127,10 @@ const PROJECT_ROOT = '../prime';
|
||||
|
||||
// Mock API middleware for development.
|
||||
const apiMiddleware: RequestHandler = (req, res, next) => {
|
||||
// Permit cross-origin embedding for decomp.me.
|
||||
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
||||
|
||||
if (!req.url || !req.headers.host || req.method !== 'GET') {
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,24 @@
|
||||
light-dark(#cecece, #3c3c3c)
|
||||
);
|
||||
|
||||
--menu-border: var(--vscode-menu-border, light-dark(#cecece, #454545));
|
||||
--menu-foreground: var(--vscode-menu-foreground, light-dark(#3b3b3b, #ccc));
|
||||
--menu-background: var(--vscode-menu-background, light-dark(#fff, #1f1f1f));
|
||||
--menu-selection-foreground: var(--vscode-menu-selectionForeground, #fff);
|
||||
--menu-selection-background: var(
|
||||
--vscode-menu-selectionBackground,
|
||||
light-dark(#005fb8, #0078d4)
|
||||
);
|
||||
--menu-separator-background: var(
|
||||
--vscode-menu-separatorBackground,
|
||||
light-dark(#d4d4d4, #454545)
|
||||
);
|
||||
|
||||
--widget-shadow: var(
|
||||
--vscode-widget-shadow,
|
||||
light-dark(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.36))
|
||||
);
|
||||
|
||||
color-scheme: light dark;
|
||||
|
||||
font-synthesis: none;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
.context-menu {
|
||||
z-index: 99;
|
||||
outline: 1px solid var(--menu-border);
|
||||
border-radius: 5px;
|
||||
color: var(--menu-foreground);
|
||||
background-color: var(--menu-background);
|
||||
box-shadow: 0 2px 8px var(--widget-shadow);
|
||||
padding: 4px 0;
|
||||
max-width: min(500px, 80vw);
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
font-family: var(--code-font-family);
|
||||
font-weight: var(--code-font-weight);
|
||||
font-size: var(--code-font-size);
|
||||
cursor: pointer;
|
||||
height: 1.5em;
|
||||
color: var(--menu-foreground);
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin: 0 4px;
|
||||
border-radius: 4px;
|
||||
padding: 0 0.25em;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-background-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--button-active-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-item-label {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.context-menu-item-value {
|
||||
flex: 0 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--color-bright);
|
||||
}
|
||||
|
||||
.context-menu-separator {
|
||||
border-color: var(--color-muted);
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
import type { display } from 'objdiff-wasm';
|
||||
import styles from './ContextMenu.module.css';
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
export type ContextMenuCallback<T> = (
|
||||
e: React.MouseEvent<HTMLElement>,
|
||||
data: T,
|
||||
) => void;
|
||||
|
||||
export type ContextMenuState<T> = Readonly<{
|
||||
visible: boolean;
|
||||
clickPosition: { x: number; y: number };
|
||||
position: { x: number; y: number };
|
||||
target: HTMLElement;
|
||||
data: T;
|
||||
}>;
|
||||
|
||||
type ContextMenuProps<T> = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
render?: (state: ContextMenuState<T>, close: () => void) => React.ReactNode;
|
||||
}>;
|
||||
|
||||
export function createContextMenu<T>(): {
|
||||
ContextMenuProvider: React.FC<ContextMenuProps<T>>;
|
||||
useContextMenu: () => ContextMenuCallback<T>;
|
||||
} {
|
||||
const Context = createContext<ContextMenuCallback<T>>((e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
return {
|
||||
ContextMenuProvider: ({
|
||||
children,
|
||||
className,
|
||||
render,
|
||||
}: ContextMenuProps<T>) => {
|
||||
const elemRef = useRef<HTMLDivElement | null>(null);
|
||||
const [state, setState] = useState<ContextMenuState<T> | null>(null);
|
||||
|
||||
const callback = useCallback<ContextMenuCallback<T>>((e, data) => {
|
||||
e.preventDefault();
|
||||
setState({
|
||||
visible: false,
|
||||
clickPosition: { x: e.clientX, y: e.clientY },
|
||||
position: { x: e.clientX, y: e.clientY },
|
||||
target: e.target as HTMLElement,
|
||||
data,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setState(null);
|
||||
}, []);
|
||||
|
||||
const closeIfOutside = useCallback(
|
||||
(e: Event) => {
|
||||
if (elemRef.current && !elemRef.current.contains(e.target as Node)) {
|
||||
close();
|
||||
}
|
||||
},
|
||||
[close],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const clickOptions = { capture: true };
|
||||
const scrollOptions = { capture: true, passive: true };
|
||||
const resizeOptions = { passive: true };
|
||||
|
||||
document.addEventListener('click', closeIfOutside, clickOptions);
|
||||
document.addEventListener('mousedown', closeIfOutside, clickOptions);
|
||||
document.addEventListener('scroll', closeIfOutside, scrollOptions);
|
||||
window.addEventListener('resize', close, resizeOptions);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', closeIfOutside, clickOptions);
|
||||
document.removeEventListener(
|
||||
'mousedown',
|
||||
closeIfOutside,
|
||||
clickOptions,
|
||||
);
|
||||
document.removeEventListener('scroll', closeIfOutside, {
|
||||
capture: true,
|
||||
});
|
||||
window.removeEventListener('resize', close);
|
||||
};
|
||||
}, [closeIfOutside, close]);
|
||||
|
||||
// Close context menu if target element is removed
|
||||
useEffect(() => {
|
||||
if (state?.target?.parentNode) {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
// biome-ignore lint/complexity/noForEach: NodeList
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
if (node === state.target) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
observer.observe(state.target.parentNode, {
|
||||
childList: true,
|
||||
});
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}
|
||||
}, [state?.target, close]);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: on purpose
|
||||
useEffect(() => {
|
||||
const tooltip = elemRef.current;
|
||||
if (!state?.clickPosition || !tooltip) {
|
||||
return;
|
||||
}
|
||||
const rect = tooltip.getBoundingClientRect();
|
||||
let x = state.clickPosition.x;
|
||||
let y = state.target.getBoundingClientRect().bottom;
|
||||
if (x < 10) {
|
||||
x = 10;
|
||||
} else if (x + rect.width > window.innerWidth - 10) {
|
||||
x = window.innerWidth - 10 - rect.width;
|
||||
}
|
||||
if (y + rect.height > window.innerHeight - 10) {
|
||||
y = window.innerHeight - 10 - rect.height;
|
||||
}
|
||||
setState((prev) => ({
|
||||
...prev!,
|
||||
position: { x, y },
|
||||
visible: true,
|
||||
}));
|
||||
}, [state?.clickPosition]);
|
||||
|
||||
let tooltip: React.ReactNode = null;
|
||||
if (state?.position) {
|
||||
const children = render?.(state, close);
|
||||
if (children) {
|
||||
tooltip = (
|
||||
<div
|
||||
ref={elemRef}
|
||||
className={
|
||||
styles.contextMenu + (className ? ` ${className}` : '')
|
||||
}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: state.position.y,
|
||||
left: state.position.x,
|
||||
visibility: state.visible ? 'visible' : 'hidden',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Context.Provider value={callback}>{children}</Context.Provider>
|
||||
{tooltip}
|
||||
</>
|
||||
);
|
||||
},
|
||||
useContextMenu: () => useContext(Context),
|
||||
};
|
||||
}
|
||||
|
||||
export function renderContextItems(
|
||||
items: display.ContextItem[],
|
||||
close: () => void,
|
||||
): React.ReactNode {
|
||||
return items.map((item, i) => {
|
||||
const key = `${item.tag}-${i}`;
|
||||
switch (item.tag) {
|
||||
case 'copy':
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={styles.contextMenuItem}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(item.val.value).then(
|
||||
() => {
|
||||
close();
|
||||
},
|
||||
(e) => {
|
||||
console.warn('Failed to copy:', e);
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<span className={styles.contextMenuItemLabel}>Copy "</span>
|
||||
<span className={styles.contextMenuItemValue}>
|
||||
{item.val.value}
|
||||
</span>
|
||||
<span className={styles.contextMenuItemLabel}>
|
||||
"{item.val.label ? ` (${item.val.label})` : ''}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 'navigate':
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={styles.contextMenuItem}
|
||||
onClick={() => {
|
||||
// TODO
|
||||
}}
|
||||
>
|
||||
{item.val.label}
|
||||
</div>
|
||||
);
|
||||
case 'separator':
|
||||
return <hr key={key} className={styles.contextMenuSeparator} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList, areEqual } from 'react-window';
|
||||
import type { ListChildComponentProps } from 'react-window';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { createContextMenu, renderContextItems } from '../common/ContextMenu';
|
||||
import TooltipShared from '../common/TooltipShared';
|
||||
import {
|
||||
buildDiffConfig,
|
||||
@@ -36,6 +37,11 @@ const ROTATION_CLASSES = [
|
||||
styles.rotation8,
|
||||
];
|
||||
|
||||
const { ContextMenuProvider, useContextMenu } = createContextMenu<{
|
||||
column: number;
|
||||
row: number;
|
||||
}>();
|
||||
|
||||
const AsmCell = ({
|
||||
obj,
|
||||
config,
|
||||
@@ -53,6 +59,7 @@ const AsmCell = ({
|
||||
highlight: HighlightState;
|
||||
setHighlight: (highlight: HighlightState) => void;
|
||||
}) => {
|
||||
const onContextMenu = useContextMenu();
|
||||
if (!obj || !symbol) {
|
||||
return <div className={styles.instructionCell} />;
|
||||
}
|
||||
@@ -197,6 +204,7 @@ const AsmCell = ({
|
||||
className={clsx(classes)}
|
||||
data-tooltip-id="instruction-tooltip"
|
||||
data-tooltip-content={JSON.stringify(tooltipContent)}
|
||||
onContextMenu={(e) => onContextMenu(e, { column, row })}
|
||||
>
|
||||
{out}
|
||||
</div>
|
||||
@@ -413,28 +421,57 @@ const FunctionView = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.instructionList}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
itemCount={itemData.itemCount}
|
||||
itemSize={itemSize}
|
||||
width={width}
|
||||
itemData={itemData}
|
||||
overscanCount={20}
|
||||
onScroll={(e) => {
|
||||
setSymbolScrollOffset(
|
||||
currentUnitName,
|
||||
itemData.symbolName,
|
||||
e.scrollOffset,
|
||||
);
|
||||
}}
|
||||
initialScrollOffset={initialScrollOffset}
|
||||
>
|
||||
{AsmRow}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<ContextMenuProvider
|
||||
render={({ data }, close) => {
|
||||
let obj: diff.ObjectDiff | undefined;
|
||||
let symbol: display.SectionDisplaySymbol | undefined;
|
||||
switch (data.column) {
|
||||
case 0:
|
||||
obj = diff.left;
|
||||
symbol = itemData.left ?? undefined;
|
||||
break;
|
||||
case 1:
|
||||
obj = diff.right;
|
||||
symbol = itemData.right ?? undefined;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!obj || !symbol) {
|
||||
return null;
|
||||
}
|
||||
const items = display.instructionContext(
|
||||
obj,
|
||||
symbol,
|
||||
data.row,
|
||||
itemData.config,
|
||||
);
|
||||
return renderContextItems(items, close);
|
||||
}}
|
||||
>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
itemCount={itemData.itemCount}
|
||||
itemSize={itemSize}
|
||||
width={width}
|
||||
itemData={itemData}
|
||||
overscanCount={20}
|
||||
onScroll={(e) => {
|
||||
setSymbolScrollOffset(
|
||||
currentUnitName,
|
||||
itemData.symbolName,
|
||||
e.scrollOffset,
|
||||
);
|
||||
}}
|
||||
initialScrollOffset={initialScrollOffset}
|
||||
>
|
||||
{AsmRow}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</ContextMenuProvider>
|
||||
</div>
|
||||
<TooltipShared
|
||||
id="instruction-tooltip"
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
areEqual,
|
||||
} from 'react-window';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { createContextMenu, renderContextItems } from '../common/ContextMenu';
|
||||
import TooltipShared from '../common/TooltipShared';
|
||||
import {
|
||||
type UnitScrollOffsets,
|
||||
@@ -22,6 +23,14 @@ import {
|
||||
} from '../state';
|
||||
import { percentClass, useFontSize } from '../util/util';
|
||||
|
||||
type SymbolTooltipContent = {
|
||||
symbolRef: display.SectionDisplaySymbol;
|
||||
side: keyof UnitScrollOffsets;
|
||||
};
|
||||
|
||||
const { ContextMenuProvider, useContextMenu } =
|
||||
createContextMenu<SymbolTooltipContent>();
|
||||
|
||||
const SectionRow = ({
|
||||
section,
|
||||
style,
|
||||
@@ -70,6 +79,7 @@ const SymbolRow = ({
|
||||
style?: React.CSSProperties;
|
||||
}) => {
|
||||
const setSelectedSymbol = useAppStore((state) => state.setSelectedSymbol);
|
||||
const onContextMenu = useContextMenu();
|
||||
const symbol = display.displaySymbol(obj, symbolRef);
|
||||
const flags = [];
|
||||
if (symbol.flags.global) {
|
||||
@@ -136,14 +146,11 @@ const SymbolRow = ({
|
||||
},
|
||||
);
|
||||
}}
|
||||
data-vscode-context={JSON.stringify({
|
||||
contextType: 'symbol',
|
||||
preventDefaultContextMenuItems: true,
|
||||
symbolName: symbol.name,
|
||||
symbolDemangledName: symbol.demangledName,
|
||||
})}
|
||||
data-tooltip-id="symbol-tooltip"
|
||||
data-tooltip-content={JSON.stringify(tooltipContent)}
|
||||
onContextMenu={(e) => {
|
||||
onContextMenu(e, tooltipContent);
|
||||
}}
|
||||
>
|
||||
{flagsElem}
|
||||
{percentElem}
|
||||
@@ -154,11 +161,6 @@ const SymbolRow = ({
|
||||
);
|
||||
};
|
||||
|
||||
type SymbolTooltipContent = {
|
||||
symbolRef: display.SectionDisplaySymbol;
|
||||
side: keyof UnitScrollOffsets;
|
||||
};
|
||||
|
||||
type SectionData = display.SectionDisplay & { collapsed: boolean };
|
||||
|
||||
type ItemData = {
|
||||
@@ -434,14 +436,35 @@ const SymbolsView = ({ diff }: { diff: diff.DiffResult }) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.symbols}>
|
||||
<AutoSizer className={styles.symbols}>
|
||||
{({ height, width }) => (
|
||||
<>
|
||||
{renderList(height, width, leftItemData, 'left')}
|
||||
{renderList(height, width, rightItemData, 'right')}
|
||||
</>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<ContextMenuProvider
|
||||
render={({ data }, close) => {
|
||||
let obj: diff.ObjectDiff | undefined;
|
||||
switch (data.side) {
|
||||
case 'left':
|
||||
obj = diff.left;
|
||||
break;
|
||||
case 'right':
|
||||
obj = diff.right;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!obj) {
|
||||
return null;
|
||||
}
|
||||
const items = display.symbolContext(obj, data.symbolRef);
|
||||
return renderContextItems(items, close);
|
||||
}}
|
||||
>
|
||||
<AutoSizer className={styles.symbols}>
|
||||
{({ height, width }) => (
|
||||
<>
|
||||
{renderList(height, width, leftItemData, 'left')}
|
||||
{renderList(height, width, rightItemData, 'right')}
|
||||
</>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</ContextMenuProvider>
|
||||
</div>
|
||||
<TooltipShared
|
||||
id="symbol-tooltip"
|
||||
|
||||
Reference in New Issue
Block a user