Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bf4b5e8

Browse files
Merge pull request #16 from utkuakyuz/feature/secondary-minimap-and-optimization
Optimize Viewer: Single Diff Calculation with Dual Minimap Display
2 parents 20c71a3 + 28f69ff commit bf4b5e8

File tree

8 files changed

+128
-35
lines changed

8 files changed

+128
-35
lines changed

‎.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ yarn-debug.log*
66
yarn-error.log*
77
pnpm-debug.log*
88
lerna-debug.log*
9+
todo.md
910

1011
node_modules
1112
dist

‎package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@
3737
],
3838
"scripts": {
3939
"build": "bun run rollup",
40+
"watch": "bun run rollup --watch",
4041
"prepublishOnly": "bun run build",
4142
"rollup": "rollup -c --bundleConfigAsCjs",
4243
"lint": "eslint",
4344
"lint:fix": "eslint --fix",
4445
"demo:dev": "cd demo && npm run dev",
45-
"demo:build": "cd demo && npm run build"
46+
"demo:build": "cd demo && npm run build",
47+
"dev:watch": "concurrently \"npm run watch\" \"npm run demo:dev\""
4648
},
4749
"peerDependencies": {
4850
"react": ">=18.0.0"
@@ -63,6 +65,7 @@
6365
"@types/node": "^22.14.1",
6466
"@types/react": ">=18.0.0",
6567
"@types/react-window": "^1.8.8",
68+
"concurrently": "^8.2.2",
6669
"eslint": "^9.33.0",
6770
"eslint-plugin-format": "^1.0.1",
6871
"eslint-plugin-react-hooks": "^5.2.0",

‎src/components/DiffViewer/components/DiffMinimap.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const DiffMinimap: React.FC<DiffMinimapProps> = ({
4747

4848
const { drawMinimap, drawScrollBox } = useMinimapDraw({
4949
canvasRef,
50+
containerRef,
5051
height,
5152
miniMapWidth,
5253
leftDiff,
@@ -98,6 +99,7 @@ export const DiffMinimap: React.FC<DiffMinimapProps> = ({
9899
return (
99100
<div
100101
ref={containerRef}
102+
className="minimap-wrapper"
101103
style={{
102104
width: miniMapWidth,
103105
height,

‎src/components/DiffViewer/components/ViewerRow.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ function ViewerRow({
2828
<button onClick={() => onExpand(originalLeftLine.segmentIndex)} className="text-blue-500 underline">
2929
Show Hidden Lines
3030
</button>
31+
<button onClick={() => onExpand(originalLeftLine.segmentIndex)} className="text-blue-500 underline">
32+
Show Hidden Lines
33+
</button>
3134
</div>
3235
);
3336
}

‎src/components/DiffViewer/components/VirtualizedDiffViewer.tsx

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const VirtualizedDiffViewer: React.FC<VirtualizedDiffViewerProps> = ({
2323
leftTitle,
2424
rightTitle,
2525
hideSearch,
26+
showSingleMinimap,
2627
onSearchMatch,
2728
differOptions,
2829
className,
@@ -155,6 +156,17 @@ export const VirtualizedDiffViewer: React.FC<VirtualizedDiffViewerProps> = ({
155156
[leftView, rightView, handleExpand, searchState.term, inlineDiffOptions],
156157
);
157158

159+
const minimapProps = {
160+
leftDiff: leftView,
161+
rightDiff: rightView,
162+
height,
163+
miniMapWidth,
164+
currentScrollTop: scrollTop,
165+
searchResults: searchState.results,
166+
currentMatchIndex: searchState.currentIndex,
167+
onScroll: (scrollTop: number) => listRef.current?.scrollTo(scrollTop),
168+
};
169+
158170
return (
159171
<div className={`diff-viewer-container${className ? ` ${className}` : ""}`}>
160172
{/* Header & Search */}
@@ -194,7 +206,7 @@ export const VirtualizedDiffViewer: React.FC<VirtualizedDiffViewerProps> = ({
194206
</div>
195207

196208
{/* List & Minimap */}
197-
<div style={{ display: "flex", gap: "8px" }}>
209+
<div style={{ display: "flex", gap: "8px",position: "relative" }}>
198210
<List
199211
ref={listRef}
200212
className="virtual-json-diff-list-container"
@@ -209,16 +221,17 @@ export const VirtualizedDiffViewer: React.FC<VirtualizedDiffViewerProps> = ({
209221
{ViewerRow}
210222
</List>
211223

212-
<DiffMinimap
213-
leftDiff={leftView}
214-
rightDiff={rightView}
215-
height={height}
216-
miniMapWidth={miniMapWidth}
217-
currentScrollTop={scrollTop}
218-
searchResults={searchState.results}
219-
currentMatchIndex={searchState.currentIndex}
220-
onScroll={scrollTop => listRef.current?.scrollTo(scrollTop)}
221-
/>
224+
<div className="minimap-overlay">
225+
<div className="half left-map-holder">
226+
{!showSingleMinimap && (
227+
<DiffMinimap {...minimapProps} />
228+
229+
)}
230+
</div>
231+
<div className="half right-map-holder">
232+
<DiffMinimap {...minimapProps} />
233+
</div>
234+
</div>
222235
</div>
223236

224237
{/* Hide All Expanded Lines Button */}

‎src/components/DiffViewer/hooks/useMinimapDraw.ts

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect } from "react";
1+
import { useCallback, useEffect,useMemo } from "react";
22

33
import type { DiffRowOrCollapsed } from "../types";
44

@@ -13,6 +13,7 @@ const MINIMAP_SCROLL_COLOR = "#7B7B7B80";
1313

1414
type Props = {
1515
canvasRef: React.RefObject<HTMLCanvasElement | null>;
16+
containerRef: React.RefObject<HTMLDivElement | null>;
1617
height: number;
1718
miniMapWidth: number;
1819
leftDiff: DiffRowOrCollapsed[];
@@ -28,6 +29,7 @@ type Props = {
2829

2930
export function useMinimapDraw({
3031
canvasRef,
32+
containerRef,
3133
height,
3234
miniMapWidth,
3335
leftDiff,
@@ -64,45 +66,67 @@ export function useMinimapDraw({
6466
ctx.fillRect(x, y, width, ROW_HEIGHT);
6567
}, [ROW_HEIGHT]);
6668

67-
// Draw the differences -> This will be called in drawScrollBox method
68-
const drawDifferencesInMinimap = useCallback((ctx: CanvasRenderingContext2D) => {
69-
const scale = height / totalLines;
69+
const diffCanvas = useMemo(() => {
70+
const offscreen = document.createElement("canvas");
71+
offscreen.width = miniMapWidth;
72+
offscreen.height = height;
73+
const ctx = offscreen.getContext("2d");
74+
if (!ctx)
75+
return null;
7076

71-
if (currentMatchIndex >= 0 && searchResults[currentMatchIndex] !== undefined) {
72-
const y = searchResults[currentMatchIndex] * scale;
73-
const lineHeight = Math.max(1, scale);
74-
ctx.fillStyle = CURRENT_MATCH_COLOR;
75-
ctx.fillRect(0, y, miniMapWidth, lineHeight);
76-
}
77+
const scale = height / totalLines;
7778

79+
// left diff
7880
leftDiff.forEach((line, index) => {
7981
const y = index * scale;
8082
drawLine(ctx, line, y, 0, miniMapWidth / 2);
8183
});
8284

85+
// right diff
8386
rightDiff.forEach((line, index) => {
8487
const y = index * scale;
8588
drawLine(ctx, line, y, miniMapWidth / 2, miniMapWidth / 2);
8689
});
8790

91+
// search highlights
8892
searchResults.forEach((index) => {
8993
const y = index * scale;
9094
const lineHeight = Math.max(1, scale);
9195
ctx.fillStyle = SEARCH_HIGHLIGHT_COLOR;
9296
ctx.fillRect(0, y, miniMapWidth, lineHeight);
9397
});
94-
}, [height, totalLines, currentMatchIndex, searchResults, leftDiff, rightDiff, miniMapWidth, drawLine]);
9598

96-
// Draw the scroll box and also differences in minimapo
97-
const drawScrollBox = useCallback((ctx: CanvasRenderingContext2D, color: string) => {
98-
const totalContentHeight = totalLines * ROW_HEIGHT;
99-
const viewportTop = (currentScrollTop / totalContentHeight) * height;
100-
101-
drawDifferencesInMinimap(ctx);
99+
return offscreen;
100+
}, [leftDiff, rightDiff, searchResults, height, totalLines, miniMapWidth, drawLine]);
102101

103-
ctx.fillStyle = color;
104-
ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight);
105-
}, [totalLines, ROW_HEIGHT, currentScrollTop, height, drawDifferencesInMinimap, miniMapWidth, viewportHeight]);
102+
// Draw the scroll box and also differences in minimapo
103+
const drawScrollBox = useCallback(
104+
(ctx: CanvasRenderingContext2D, color: string) => {
105+
if (!diffCanvas)
106+
return;
107+
108+
// Copy pre-rendered diff
109+
ctx.clearRect(0, 0, miniMapWidth, height);
110+
ctx.drawImage(diffCanvas, 0, 0);
111+
112+
// Draw scroll box
113+
const totalContentHeight = totalLines * ROW_HEIGHT;
114+
const viewportTop = (currentScrollTop / totalContentHeight) * height;
115+
116+
ctx.fillStyle = color;
117+
ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight);
118+
119+
// Draw current match highlight (optional)
120+
if (currentMatchIndex >= 0 && searchResults[currentMatchIndex] !== undefined) {
121+
const scale = height / totalLines;
122+
const y = searchResults[currentMatchIndex] * scale;
123+
const lineHeight = Math.max(1, scale);
124+
ctx.fillStyle = CURRENT_MATCH_COLOR;
125+
ctx.fillRect(0, y, miniMapWidth, lineHeight);
126+
}
127+
},
128+
[diffCanvas, currentScrollTop, totalLines, ROW_HEIGHT, height, miniMapWidth, viewportHeight, currentMatchIndex, searchResults],
129+
);
106130

107131
const drawMinimap = useCallback(() => {
108132
const canvas = canvasRef.current;
@@ -116,12 +140,18 @@ export function useMinimapDraw({
116140
ctx.clearRect(0, 0, canvas.width, canvas.height);
117141

118142
if (!isDragging.current) {
143+
if (containerRef.current) {
144+
containerRef.current.style.opacity = "0.65";
145+
}
119146
drawScrollBox(ctx, MINIMAP_SCROLL_COLOR);
120147
}
121148
else {
149+
if (containerRef.current) {
150+
containerRef.current.style.opacity = "0.85";
151+
}
122152
drawScrollBox(ctx, MINIMAP_HOVER_SCROLL_COLOR);
123153
}
124-
}, [leftDiff,rightDiff,height,currentScrollTop,searchResults,currentMatchIndex,drawLine,viewportHeight]);
154+
}, [drawScrollBox]);
125155

126156
useEffect(() => {
127157
drawMinimap();

‎src/components/DiffViewer/styles/JsonDiffCustomTheme.css

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
}
44

55
.virtual-json-diff-list-container {
6+
scrollbar-width: none;
67
--sb-track-color: #232e33;
78
--sb-thumb-color: #4560f8;
89
--sb-size: 6px;
@@ -38,6 +39,8 @@
3839
.json-diff-viewer.json-diff-viewer-theme-custom tr td.line-number {
3940
color: #999;
4041
padding: 2px;
42+
padding-right: 6px;
43+
text-align: right;
4144
}
4245

4346
.json-diff-viewer.json-diff-viewer-theme-custom table {
@@ -68,7 +71,7 @@
6871
overflow: hidden;
6972
margin: 0;
7073
padding-left: 4px;
71-
margin-right: 4px;
74+
margin-right: 29px;
7275
font-size: 12px;
7376
min-width: 300px;
7477
line-height: var(--diff-row-height);
@@ -166,7 +169,7 @@
166169
.collapsed-button {
167170
display: flex;
168171
background-color: #262626;
169-
justify-content: center;
172+
justify-content: space-around;
170173
align-items: center;
171174
}
172175

@@ -292,20 +295,57 @@
292295

293296
.virtual-json-diff-list-container::-webkit-scrollbar {
294297
width: var(--sb-size);
298+
display: none;
295299
}
296300

297301
.virtual-json-diff-list-container::-webkit-scrollbar-track {
298302
background: var(--sb-track-color);
303+
display: none;
299304
border-radius: 3px;
300305
}
301306

302307
.virtual-json-diff-list-container::-webkit-scrollbar-thumb {
303308
background: var(--sb-thumb-color);
304309
border-radius: 3px;
310+
display: none;
305311
}
306312

307313
@supports not selector(::-webkit-scrollbar) {
308314
.virtual-json-diff-list-container {
309315
scrollbar-color: var(--sb-thumb-color) var(--sb-track-color);
310316
}
311317
}
318+
319+
/* MINIMAP IMPROVEMENTS */
320+
.diff-viewer-container .minimap-overlay {
321+
position: absolute;
322+
top: 0;
323+
left: 0;
324+
width: 100%;
325+
display: flex;
326+
pointer-events: none;
327+
}
328+
329+
.diff-viewer-container .minimap-overlay .half {
330+
flex: 1;
331+
display: flex;
332+
align-items: center;
333+
}
334+
335+
.diff-viewer-container .minimap-overlay .left-map-holder {
336+
justify-content: flex-end;
337+
min-width: 334px;
338+
}
339+
340+
.diff-viewer-container .minimap-overlay .right-map-holder {
341+
justify-content: flex-end;
342+
}
343+
344+
.diff-viewer-container .minimap-overlay .minimap-wrapper {
345+
opacity: 0.65;
346+
pointer-events: auto;
347+
}
348+
349+
.diff-viewer-container .minimap-overlay .minimap-wrapper:hover {
350+
opacity: 0.85;
351+
}

‎src/components/DiffViewer/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type VirtualizedDiffViewerProps = {
4242
rightTitle?: string;
4343
onSearchMatch?: (index: number) => void;
4444
differOptions?: DifferOptions;
45+
showSingleMinimap?: boolean;
4546
className?: string;
4647
miniMapWidth?: number;
4748
inlineDiffOptions?: InlineDiffOptions;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /