You've already forked objdiff-web
mirror of
https://github.com/encounter/objdiff-web.git
synced 2026-03-30 11:32:18 -07:00
Improve Tooltip component & memoization cleanup
This commit is contained in:
@@ -1,81 +1,57 @@
|
||||
import clsx from 'clsx';
|
||||
import styles from './TooltipShared.module.css';
|
||||
|
||||
import type { display } from 'objdiff-wasm';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
|
||||
type TooltipProps = {
|
||||
export type TooltipTriggerProps = {
|
||||
'data-tooltip-id': string;
|
||||
'data-tooltip-content': string;
|
||||
};
|
||||
|
||||
export type TooltipCallback<T> = (content: T) => display.HoverItem[] | null;
|
||||
|
||||
export type TooltipProps<T> = Omit<
|
||||
React.ComponentProps<typeof Tooltip>,
|
||||
'id' | 'render'
|
||||
> & {
|
||||
callback: TooltipCallback<T>;
|
||||
};
|
||||
|
||||
export function createTooltip<T>(): {
|
||||
Tooltip: React.FC<{
|
||||
callback: TooltipCallback<T>;
|
||||
}>;
|
||||
useTooltip: (content: T) => TooltipProps;
|
||||
Tooltip: React.FC<TooltipProps<T>>;
|
||||
useTooltip: (content: T) => TooltipTriggerProps;
|
||||
} {
|
||||
const id = generateRandomString(10);
|
||||
return {
|
||||
Tooltip: ({ callback }) => {
|
||||
const callbackMemo = useCallback(
|
||||
(content: string) => {
|
||||
if (!content) {
|
||||
Tooltip: ({ callback, className, ...props }) => (
|
||||
<Tooltip
|
||||
{...props}
|
||||
id={id}
|
||||
className={clsx(styles.tooltip, className)}
|
||||
render={({ content }) => {
|
||||
const items = useMemo(() => {
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
const parsedContent = JSON.parse(content) as T;
|
||||
return callback(parsedContent);
|
||||
}, [callback, content]);
|
||||
if (!items) {
|
||||
return null;
|
||||
}
|
||||
const parsedContent = JSON.parse(content) as T;
|
||||
return callback(parsedContent);
|
||||
},
|
||||
[callback],
|
||||
);
|
||||
return <TooltipShared id={id} callback={callbackMemo} />;
|
||||
},
|
||||
useTooltip: (content: T) =>
|
||||
// useMemo(
|
||||
// () => ({
|
||||
// 'data-tooltip-id': id,
|
||||
// 'data-tooltip-content': JSON.stringify(content),
|
||||
// }),
|
||||
// [content],
|
||||
// ),
|
||||
({
|
||||
'data-tooltip-id': id,
|
||||
'data-tooltip-content': JSON.stringify(content),
|
||||
}),
|
||||
return <TooltipContentMemo items={items} />;
|
||||
}}
|
||||
/>
|
||||
),
|
||||
useTooltip: (content: T) => ({
|
||||
'data-tooltip-id': id,
|
||||
'data-tooltip-content': JSON.stringify(content),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const TooltipShared = ({
|
||||
id,
|
||||
callback,
|
||||
}: {
|
||||
id: string;
|
||||
callback: (content: string) => display.HoverItem[] | null;
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip
|
||||
id={id}
|
||||
place="bottom"
|
||||
className={styles.tooltip}
|
||||
delayShow={500}
|
||||
render={({ content }) => {
|
||||
const items = useMemo(() => {
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
return callback(content);
|
||||
}, [callback, content]);
|
||||
if (!items) {
|
||||
return null;
|
||||
}
|
||||
return <TooltipContentMemo items={items} />;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipContent = ({ items }: { items: display.HoverItem[] }) => {
|
||||
const out = [];
|
||||
for (const [i, item] of items.entries()) {
|
||||
|
||||
+12
-14
@@ -1,7 +1,7 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { diff } from 'objdiff-wasm';
|
||||
import { subscribeWithSelector } from 'zustand/middleware';
|
||||
import { create } from 'zustand/react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import {
|
||||
type ConfigProperties,
|
||||
type ConfigPropertyValue,
|
||||
@@ -325,20 +325,18 @@ export function openSettings(): void {
|
||||
vsCode.postMessage({ type: 'openSettings' });
|
||||
}
|
||||
|
||||
export function buildDiffConfig(
|
||||
configProperties: ConfigProperties | null | undefined,
|
||||
): diff.DiffConfig {
|
||||
const config = new diff.DiffConfig();
|
||||
const props = getModifiedConfigProperties(
|
||||
configProperties ?? useExtensionStore.getState().configProperties,
|
||||
);
|
||||
for (const key in props) {
|
||||
if (props[key] != null) {
|
||||
config.setProperty(key, props[key].toString());
|
||||
export const buildDiffConfig = memoizeOne(
|
||||
(configProperties: ConfigProperties): diff.DiffConfig => {
|
||||
const config = new diff.DiffConfig();
|
||||
const props = getModifiedConfigProperties(configProperties);
|
||||
for (const key in props) {
|
||||
if (props[key] != null) {
|
||||
config.setProperty(key, props[key].toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
);
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const message = event.data as InboundMessage;
|
||||
|
||||
@@ -101,10 +101,8 @@ const DiffView = ({
|
||||
() => resolveSymbol(result.diff?.right, rightSymbolRef),
|
||||
[result.diff?.right, rightSymbolRef],
|
||||
);
|
||||
const diffConfig = useMemo(
|
||||
() => buildDiffConfig(configProperties),
|
||||
[configProperties],
|
||||
);
|
||||
// Already memoized
|
||||
const diffConfig = buildDiffConfig(configProperties);
|
||||
|
||||
let leftColumnView: ColumnView = {
|
||||
type: 'symbols',
|
||||
@@ -317,8 +315,16 @@ const DiffView = ({
|
||||
</InstructionContextMenuProvider>
|
||||
</SymbolContextMenuProvider>
|
||||
</div>
|
||||
<SymbolTooltip callback={symbolTooltipCallback} />
|
||||
<InstructionTooltip callback={instructionTooltipCallback} />
|
||||
<SymbolTooltip
|
||||
place="bottom"
|
||||
delayShow={500}
|
||||
callback={symbolTooltipCallback}
|
||||
/>
|
||||
<InstructionTooltip
|
||||
place="bottom"
|
||||
delayShow={500}
|
||||
callback={instructionTooltipCallback}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import headerStyles from '../common/Header.module.css';
|
||||
import styles from './FunctionView.module.css';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { type diff, display } from 'objdiff-wasm';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { FixedSizeList, areEqual } from 'react-window';
|
||||
import type { ListChildComponentProps } from 'react-window';
|
||||
import type { ListChildComponentProps, ListOnScrollProps } from 'react-window';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { createContextMenu, renderContextItems } from '../common/ContextMenu';
|
||||
import { createContextMenu } from '../common/ContextMenu';
|
||||
import { createTooltip } from '../common/TooltipShared';
|
||||
import { buildDiffConfig, useAppStore, useExtensionStore } from '../state';
|
||||
import {
|
||||
@@ -283,58 +281,6 @@ const AsmRow = memo(
|
||||
areEqual,
|
||||
);
|
||||
|
||||
const createItemData = memoizeOne(
|
||||
(
|
||||
result: diff.DiffResult,
|
||||
leftSymbol: display.SymbolDisplay | null,
|
||||
rightSymbol: display.SymbolDisplay | null,
|
||||
highlight: HighlightState,
|
||||
setHighlight: (highlight: HighlightState) => void,
|
||||
): ItemData => {
|
||||
const itemCount = Math.max(
|
||||
leftSymbol?.rowCount || 0,
|
||||
rightSymbol?.rowCount || 0,
|
||||
);
|
||||
const symbolName = leftSymbol?.info.name || rightSymbol?.info.name || '';
|
||||
const config = buildDiffConfig(null);
|
||||
const matchPercent = rightSymbol?.matchPercent;
|
||||
return {
|
||||
itemCount,
|
||||
symbolName,
|
||||
result,
|
||||
config,
|
||||
matchPercent,
|
||||
leftSymbol,
|
||||
rightSymbol,
|
||||
highlight,
|
||||
setHighlight,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const SymbolLabel = ({
|
||||
symbol,
|
||||
}: {
|
||||
symbol: display.SymbolDisplay | null;
|
||||
}) => {
|
||||
if (!symbol) {
|
||||
return (
|
||||
<span className={clsx(headerStyles.label, headerStyles.missing)}>
|
||||
Missing
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const displayName = symbol.info.demangledName || symbol.info.name;
|
||||
return (
|
||||
<span
|
||||
className={clsx(headerStyles.label, headerStyles.emphasized)}
|
||||
title={displayName}
|
||||
>
|
||||
{displayName}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const InstructionList = ({
|
||||
height,
|
||||
width,
|
||||
@@ -348,7 +294,12 @@ export const InstructionList = ({
|
||||
leftSymbol: display.SymbolDisplay | null;
|
||||
rightSymbol: display.SymbolDisplay | null;
|
||||
}) => {
|
||||
const currentUnit = useExtensionStore((state) => state.currentUnit);
|
||||
const { configProperties, currentUnit } = useExtensionStore(
|
||||
useShallow((state) => ({
|
||||
configProperties: state.configProperties,
|
||||
currentUnit: state.currentUnit,
|
||||
})),
|
||||
);
|
||||
const { highlight, setSymbolScrollOffset, setHighlight } = useAppStore(
|
||||
useShallow((state) => ({
|
||||
highlight: state.highlight,
|
||||
@@ -356,13 +307,33 @@ export const InstructionList = ({
|
||||
setHighlight: state.setHighlight,
|
||||
})),
|
||||
);
|
||||
const itemData = createItemData(
|
||||
const itemData = useMemo(() => {
|
||||
const itemCount = Math.max(
|
||||
leftSymbol?.rowCount || 0,
|
||||
rightSymbol?.rowCount || 0,
|
||||
);
|
||||
const symbolName = leftSymbol?.info.name || rightSymbol?.info.name || '';
|
||||
const config = buildDiffConfig(configProperties);
|
||||
const matchPercent = leftSymbol?.matchPercent;
|
||||
return {
|
||||
itemCount,
|
||||
symbolName,
|
||||
result: diff,
|
||||
config,
|
||||
matchPercent,
|
||||
leftSymbol,
|
||||
rightSymbol,
|
||||
highlight,
|
||||
setHighlight,
|
||||
};
|
||||
}, [
|
||||
diff,
|
||||
leftSymbol,
|
||||
rightSymbol,
|
||||
configProperties,
|
||||
highlight,
|
||||
setHighlight,
|
||||
);
|
||||
]);
|
||||
const currentUnitName = currentUnit?.name || '';
|
||||
const initialScrollOffset = useMemo(
|
||||
() =>
|
||||
@@ -372,6 +343,16 @@ export const InstructionList = ({
|
||||
[currentUnitName, itemData.symbolName],
|
||||
);
|
||||
const itemSize = useFontSize() * 1.33;
|
||||
const onScrollMemo = useCallback(
|
||||
(e: ListOnScrollProps) => {
|
||||
setSymbolScrollOffset(
|
||||
currentUnitName,
|
||||
itemData.symbolName,
|
||||
e.scrollOffset,
|
||||
);
|
||||
},
|
||||
[currentUnitName, itemData.symbolName, setSymbolScrollOffset],
|
||||
);
|
||||
return (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
@@ -380,13 +361,7 @@ export const InstructionList = ({
|
||||
width={width}
|
||||
itemData={itemData}
|
||||
overscanCount={20}
|
||||
onScroll={(e) => {
|
||||
setSymbolScrollOffset(
|
||||
currentUnitName,
|
||||
itemData.symbolName,
|
||||
e.scrollOffset,
|
||||
);
|
||||
}}
|
||||
onScroll={onScrollMemo}
|
||||
initialScrollOffset={initialScrollOffset}
|
||||
>
|
||||
{AsmRow}
|
||||
|
||||
Reference in New Issue
Block a user